From b5b774f6a80ea71279b39a0691ae76a80d5ec021 Mon Sep 17 00:00:00 2001 From: Alessio Placitelli Date: Mon, 9 Nov 2020 11:09:02 +0000 Subject: [PATCH] Bug 1675534 - Vendor glean (Rust Language Bindings). r=chutten This is vendoring the Glean Rust Language Bindings (built on the top of glean-core), providing a nice Glean SDK Rust API for consumers, for using in FOG. Differential Revision: https://phabricator.services.mozilla.com/D96227 --- Cargo.lock | 27 + third_party/rust/glean/.cargo-checksum.json | 1 + third_party/rust/glean/Cargo.toml | 59 ++ third_party/rust/glean/LICENSE | 373 +++++++++++++ third_party/rust/glean/README.md | 46 ++ third_party/rust/glean/src/configuration.rs | 22 + third_party/rust/glean/src/core_metrics.rs | 98 ++++ .../rust/glean/src/dispatcher/global.rs | 126 +++++ third_party/rust/glean/src/dispatcher/mod.rs | 517 ++++++++++++++++++ third_party/rust/glean/src/glean_metrics.rs | 10 + third_party/rust/glean/src/lib.rs | 337 ++++++++++++ third_party/rust/glean/src/pings.rs | 57 ++ third_party/rust/glean/src/private/boolean.rs | 64 +++ third_party/rust/glean/src/private/mod.rs | 11 + third_party/rust/glean/src/private/ping.rs | 44 ++ third_party/rust/glean/src/system.rs | 55 ++ third_party/rust/glean/src/test.rs | 201 +++++++ third_party/rust/glean/tests/schema.rs | 93 ++++ .../rust/inherent/.cargo-checksum.json | 1 + third_party/rust/inherent/Cargo.toml | 42 ++ third_party/rust/inherent/LICENSE-APACHE | 201 +++++++ third_party/rust/inherent/LICENSE-MIT | 23 + third_party/rust/inherent/README.md | 104 ++++ .../rust/inherent/src/default_methods.rs | 23 + third_party/rust/inherent/src/expand.rs | 101 ++++ third_party/rust/inherent/src/lib.rs | 123 +++++ third_party/rust/inherent/src/parse.rs | 69 +++ third_party/rust/inherent/src/visibility.rs | 50 ++ .../rust/inherent/tests/compiletest.rs | 6 + third_party/rust/inherent/tests/test.rs | 27 + .../inherent/tests/ui/default-with-body.rs | 18 + .../tests/ui/default-with-body.stderr | 8 + .../inherent/tests/ui/nonsense-in-default.rs | 16 + .../tests/ui/nonsense-in-default.stderr | 5 + .../rust/inherent/tests/ui/not-trait-impl.rs | 10 + .../inherent/tests/ui/not-trait-impl.stderr | 7 + .../rust/inherent/tests/ui/not-visible.rs | 18 + .../rust/inherent/tests/ui/not-visible.stderr | 5 + toolkit/components/glean/Cargo.toml | 1 + toolkit/components/glean/api/Cargo.toml | 1 + 40 files changed, 3000 insertions(+) create mode 100644 third_party/rust/glean/.cargo-checksum.json create mode 100644 third_party/rust/glean/Cargo.toml create mode 100644 third_party/rust/glean/LICENSE create mode 100644 third_party/rust/glean/README.md create mode 100644 third_party/rust/glean/src/configuration.rs create mode 100644 third_party/rust/glean/src/core_metrics.rs create mode 100644 third_party/rust/glean/src/dispatcher/global.rs create mode 100644 third_party/rust/glean/src/dispatcher/mod.rs create mode 100644 third_party/rust/glean/src/glean_metrics.rs create mode 100644 third_party/rust/glean/src/lib.rs create mode 100644 third_party/rust/glean/src/pings.rs create mode 100644 third_party/rust/glean/src/private/boolean.rs create mode 100644 third_party/rust/glean/src/private/mod.rs create mode 100644 third_party/rust/glean/src/private/ping.rs create mode 100644 third_party/rust/glean/src/system.rs create mode 100644 third_party/rust/glean/src/test.rs create mode 100644 third_party/rust/glean/tests/schema.rs create mode 100644 third_party/rust/inherent/.cargo-checksum.json create mode 100644 third_party/rust/inherent/Cargo.toml create mode 100644 third_party/rust/inherent/LICENSE-APACHE create mode 100644 third_party/rust/inherent/LICENSE-MIT create mode 100644 third_party/rust/inherent/README.md create mode 100644 third_party/rust/inherent/src/default_methods.rs create mode 100644 third_party/rust/inherent/src/expand.rs create mode 100644 third_party/rust/inherent/src/lib.rs create mode 100644 third_party/rust/inherent/src/parse.rs create mode 100644 third_party/rust/inherent/src/visibility.rs create mode 100644 third_party/rust/inherent/tests/compiletest.rs create mode 100644 third_party/rust/inherent/tests/test.rs create mode 100644 third_party/rust/inherent/tests/ui/default-with-body.rs create mode 100644 third_party/rust/inherent/tests/ui/default-with-body.stderr create mode 100644 third_party/rust/inherent/tests/ui/nonsense-in-default.rs create mode 100644 third_party/rust/inherent/tests/ui/nonsense-in-default.stderr create mode 100644 third_party/rust/inherent/tests/ui/not-trait-impl.rs create mode 100644 third_party/rust/inherent/tests/ui/not-trait-impl.stderr create mode 100644 third_party/rust/inherent/tests/ui/not-visible.rs create mode 100644 third_party/rust/inherent/tests/ui/not-visible.stderr diff --git a/Cargo.lock b/Cargo.lock index a65218b57a8e..865dbca7f0a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1573,6 +1573,7 @@ dependencies = [ "chrono", "crossbeam-channel", "ffi-support", + "glean", "glean-core", "log", "nsstring", @@ -1597,6 +1598,7 @@ version = "0.1.0" dependencies = [ "cstr", "fog", + "glean", "glean-core", "log", "nserror", @@ -2104,6 +2106,20 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "glean" +version = "33.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b392b31d576ae3b226b692cbc580b1a36700de162ae5570cfe2694f38edbb44" +dependencies = [ + "crossbeam-channel", + "glean-core", + "inherent", + "log", + "once_cell", + "thiserror", +] + [[package]] name = "glean-core" version = "33.1.2" @@ -2432,6 +2448,17 @@ dependencies = [ "adler32", ] +[[package]] +name = "inherent" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2db418475edf9eb55fec92b7527be6d9a7b880bff6651cb0c0345af57f1cadf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "inplace_it" version = "0.3.2" diff --git a/third_party/rust/glean/.cargo-checksum.json b/third_party/rust/glean/.cargo-checksum.json new file mode 100644 index 000000000000..192a0e063962 --- /dev/null +++ b/third_party/rust/glean/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"238e86c37e25dac4f435f657c1657a36028e57185b5ac04d0a1917d0d12d079e","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"fd9e0ca6907917ea6bec5de05e15dd21d20fae1cb7f3250467bb20231a8e1065","src/configuration.rs":"16e3ec9be802ac37b39d2ad4d1a4702d7e5b462071ef7f0265aea84de520641f","src/core_metrics.rs":"e20697e04f707c34c3c7a0cc4e2ed93e638d3028f03eb75a93f53ae722243986","src/dispatcher/global.rs":"511b4280bdde2a39921c67bb69f5bb1b88a0819d58ea466e65a2b989e6aad426","src/dispatcher/mod.rs":"b44eca1925c209f2a9b0b7c56fb22d5a71aafefef85b45b6ab64ae1f9f611223","src/glean_metrics.rs":"a5e1ea9c4dccb81aec4aa584bd76cf47e916c66af4aff4a0ef5aa297ee2d9aa3","src/lib.rs":"0fa21ed5701035935db6f589171886bb9adb4da3b123eee2b1e9908ae8438e7d","src/pings.rs":"2dfccd84848e1933aa4f6a7a707c58ec794c8f73ef2d93ea4d4df71d4e6abc31","src/private/boolean.rs":"2ead8da55eca0c8738f3c07445b46b1efa706b3e8a1e60428347e9fcb1d1fd3f","src/private/mod.rs":"7bea12098239d8d59f1833338f1691816e4b67c36770c7743492953bc1fe7fc1","src/private/ping.rs":"108d2c5121bd1fe2fabaa5f07552e9bf84cd82805fe8176ef2794cc8954459a0","src/system.rs":"ba7b3eac040abe4691d9d287562ddca6d7e92a6d6109c3f0c443b707a100d75a","src/test.rs":"6b97b76cfcca53c9b18dbb92e2bbe0dc9528d062cc3542a3144a432f686ed8d2","tests/schema.rs":"f595de24f6da945c8319910bb1a85eacef477a199a302a7c50e99925815d063f"},"package":"0b392b31d576ae3b226b692cbc580b1a36700de162ae5570cfe2694f38edbb44"} \ No newline at end of file diff --git a/third_party/rust/glean/Cargo.toml b/third_party/rust/glean/Cargo.toml new file mode 100644 index 000000000000..c33ee352d7b0 --- /dev/null +++ b/third_party/rust/glean/Cargo.toml @@ -0,0 +1,59 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "glean" +version = "33.1.2" +authors = ["Jan-Erik Rediger ", "The Glean Team "] +include = ["/README.md", "/LICENSE", "/src", "/tests", "/Cargo.toml"] +description = "Glean SDK Rust language bindings" +readme = "README.md" +keywords = ["telemetry", "glean"] +license = "MPL-2.0" +repository = "https://github.com/mozilla/glean" +[dependencies.crossbeam-channel] +version = "0.4.3" + +[dependencies.glean-core] +version = "33.1.2" + +[dependencies.inherent] +version = "0.1.4" + +[dependencies.log] +version = "0.4.8" + +[dependencies.once_cell] +version = "1.2.0" + +[dependencies.thiserror] +version = "1.0.4" +[dev-dependencies.env_logger] +version = "0.7.1" +features = ["termcolor", "atty", "humantime"] +default-features = false + +[dev-dependencies.jsonschema-valid] +version = "0.4.0" + +[dev-dependencies.serde_json] +version = "1.0.44" + +[dev-dependencies.tempfile] +version = "3.1.0" +[badges.circle-ci] +branch = "main" +repository = "mozilla/glean" + +[badges.maintenance] +status = "actively-developed" diff --git a/third_party/rust/glean/LICENSE b/third_party/rust/glean/LICENSE new file mode 100644 index 000000000000..a612ad9813b0 --- /dev/null +++ b/third_party/rust/glean/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/third_party/rust/glean/README.md b/third_party/rust/glean/README.md new file mode 100644 index 000000000000..cbe1599ab84f --- /dev/null +++ b/third_party/rust/glean/README.md @@ -0,0 +1,46 @@ +# glean + +The `Glean SDK` is a modern approach for a Telemetry library and is part of the [Glean project](https://docs.telemetry.mozilla.org/concepts/glean/glean.html). + +## `glean` + +This library provides a Rust language bindings on top of `glean-core`, targeted to Rust consumers. + +**Note: `glean` is currently under development and not yet ready for use.** + +## Documentation + +All documentation is available online: + +* [The Glean SDK Book][book] +* [API documentation][apidocs] + +[book]: https://mozilla.github.io/glean/ +[apidocs]: https://mozilla.github.io/glean/docs/glean_preview/index.html + +## Example + +```rust,no_run +use glean::{Configuration, Error, metrics::*}; + +let cfg = Configuration { + data_path: "/tmp/data".into(), + application_id: "org.mozilla.glean_core.example".into(), + upload_enabled: true, + max_events: None, + delay_ping_lifetime_io: false, +}; +glean::initialize(cfg)?; + +let prototype_ping = PingType::new("prototype", true, true, vec![]); + +glean::register_ping_type(&prototype_ping); + +prototype_ping.submit(None); +``` + +## License + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/ diff --git a/third_party/rust/glean/src/configuration.rs b/third_party/rust/glean/src/configuration.rs new file mode 100644 index 000000000000..059b3d92283d --- /dev/null +++ b/third_party/rust/glean/src/configuration.rs @@ -0,0 +1,22 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/// The Glean configuration. +/// +/// Optional values will be filled in with default values. +#[derive(Debug, Clone)] +pub struct Configuration { + /// Whether upload should be enabled. + pub upload_enabled: bool, + /// Path to a directory to store all data in. + pub data_path: String, + /// The application ID (will be sanitized during initialization). + pub application_id: String, + /// The maximum number of events to store before sending a ping containing events. + pub max_events: Option, + /// Whether Glean should delay persistence of data from metrics with ping lifetime. + pub delay_ping_lifetime_io: bool, + /// The release channel the application is on, if known. + pub channel: Option, +} diff --git a/third_party/rust/glean/src/core_metrics.rs b/third_party/rust/glean/src/core_metrics.rs new file mode 100644 index 000000000000..5e5adfa7e8a5 --- /dev/null +++ b/third_party/rust/glean/src/core_metrics.rs @@ -0,0 +1,98 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use glean_core::{metrics::StringMetric, CommonMetricData, Lifetime}; + +/// Metrics included in every ping as `client_info`. +#[derive(Debug)] +pub struct ClientInfoMetrics { + /// The build identifier generated by the CI system (e.g. "1234/A"). + pub app_build: String, + /// The user visible version string (e.g. "1.0.3"). + pub app_display_version: String, +} + +impl ClientInfoMetrics { + /// Creates the client info with dummy values for all. + pub fn unknown() -> Self { + ClientInfoMetrics { + app_build: "unknown".to_string(), + app_display_version: "unknown".to_string(), + } + } +} + +#[derive(Debug)] +pub struct InternalMetrics { + pub app_build: StringMetric, + pub app_display_version: StringMetric, + pub app_channel: StringMetric, + pub os_version: StringMetric, + pub architecture: StringMetric, + pub device_manufacturer: StringMetric, + pub device_model: StringMetric, +} + +impl InternalMetrics { + pub fn new() -> Self { + Self { + app_build: StringMetric::new(CommonMetricData { + name: "app_build".into(), + category: "".into(), + send_in_pings: vec!["glean_client_info".into()], + lifetime: Lifetime::Application, + disabled: false, + dynamic_label: None, + }), + app_display_version: StringMetric::new(CommonMetricData { + name: "app_display_version".into(), + category: "".into(), + send_in_pings: vec!["glean_client_info".into()], + lifetime: Lifetime::Application, + disabled: false, + dynamic_label: None, + }), + app_channel: StringMetric::new(CommonMetricData { + name: "app_channel".into(), + category: "".into(), + send_in_pings: vec!["glean_client_info".into()], + lifetime: Lifetime::Application, + disabled: false, + dynamic_label: None, + }), + os_version: StringMetric::new(CommonMetricData { + name: "os_version".into(), + category: "".into(), + send_in_pings: vec!["glean_client_info".into()], + lifetime: Lifetime::Application, + disabled: false, + dynamic_label: None, + }), + architecture: StringMetric::new(CommonMetricData { + name: "architecture".into(), + category: "".into(), + send_in_pings: vec!["glean_client_info".into()], + lifetime: Lifetime::Application, + disabled: false, + dynamic_label: None, + }), + device_manufacturer: StringMetric::new(CommonMetricData { + name: "device_manufacturer".into(), + category: "".into(), + send_in_pings: vec!["glean_client_info".into()], + lifetime: Lifetime::Application, + disabled: false, + dynamic_label: None, + }), + device_model: StringMetric::new(CommonMetricData { + name: "device_model".into(), + category: "".into(), + send_in_pings: vec!["glean_client_info".into()], + lifetime: Lifetime::Application, + disabled: false, + dynamic_label: None, + }), + } + } +} diff --git a/third_party/rust/glean/src/dispatcher/global.rs b/third_party/rust/glean/src/dispatcher/global.rs new file mode 100644 index 000000000000..4b5a302f0f02 --- /dev/null +++ b/third_party/rust/glean/src/dispatcher/global.rs @@ -0,0 +1,126 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use once_cell::sync::{Lazy, OnceCell}; +use std::sync::RwLock; + +use super::{DispatchError, DispatchGuard, Dispatcher}; + +const GLOBAL_DISPATCHER_LIMIT: usize = 100; +static GLOBAL_DISPATCHER: Lazy>> = + Lazy::new(|| RwLock::new(Some(Dispatcher::new(GLOBAL_DISPATCHER_LIMIT)))); + +fn guard() -> &'static DispatchGuard { + static GLOBAL_GUARD: OnceCell = OnceCell::new(); + + GLOBAL_GUARD.get_or_init(|| { + let lock = GLOBAL_DISPATCHER.read().unwrap(); + lock.as_ref().unwrap().guard() + }) +} + +/// Launches a new task on the global dispatch queue. +/// +/// The new task will be enqueued immediately. +/// If the pre-init queue was already flushed, +/// the background thread will process tasks in the queue (see [`flush_init`]). +/// +/// This will not block. +/// +/// [`flush_init`]: fn.flush_init.html +pub fn launch(task: impl FnOnce() + Send + 'static) { + match guard().launch(task) { + Ok(_) => {} + Err(DispatchError::QueueFull) => { + log::info!("Exceeded maximum queue size, discarding task"); + // TODO: Record this as an error. + } + Err(_) => { + log::info!("Failed to launch a task on the queue. Discarding task."); + } + } +} + +/// Block until all tasks prior to this call are processed. +pub fn block_on_queue() { + let guard = { + GLOBAL_DISPATCHER + .write() + .unwrap() + .as_ref() + .map(|dispatcher| dispatcher.guard()) + .unwrap() + }; + + guard.block_on_queue(); +} + +/// Starts processing queued tasks in the global dispatch queue. +/// +/// This function blocks until queued tasks prior to this call are finished. +/// Once the initial queue is empty the dispatcher will wait for new tasks to be launched. +pub fn flush_init() -> Result<(), DispatchError> { + let mut guard = { + GLOBAL_DISPATCHER + .write() + .unwrap() + .as_ref() + .map(|dispatcher| dispatcher.guard()) + .unwrap() + }; + guard.flush_init() +} + +/// Shuts down the dispatch queue. +/// +/// This will initiate a shutdown of the worker thread +/// and no new tasks will be processed after this. +/// It will not block on the worker thread. +pub fn try_shutdown() -> Result<(), DispatchError> { + guard().shutdown() +} + +#[cfg(test)] +mod test { + use std::sync::{Arc, Mutex}; + + use super::*; + + #[test] + #[ignore] // We can't reset the queue at the moment, so filling it up breaks other tests. + fn global_fills_up_in_order_and_works() { + let _ = env_logger::builder().is_test(true).try_init(); + + let result = Arc::new(Mutex::new(vec![])); + + for i in 1..=GLOBAL_DISPATCHER_LIMIT { + let result = Arc::clone(&result); + launch(move || { + result.lock().unwrap().push(i); + }); + } + + { + let result = Arc::clone(&result); + launch(move || { + result.lock().unwrap().push(150); + }); + } + + flush_init().unwrap(); + + { + let result = Arc::clone(&result); + launch(move || { + result.lock().unwrap().push(200); + }); + } + + block_on_queue(); + + let mut expected = (1..=GLOBAL_DISPATCHER_LIMIT).collect::>(); + expected.push(200); + assert_eq!(&*result.lock().unwrap(), &expected); + } +} diff --git a/third_party/rust/glean/src/dispatcher/mod.rs b/third_party/rust/glean/src/dispatcher/mod.rs new file mode 100644 index 000000000000..27ff63c453aa --- /dev/null +++ b/third_party/rust/glean/src/dispatcher/mod.rs @@ -0,0 +1,517 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! A global dispatcher queue. +//! +//! # Example - Global Dispatch queue +//! +//! The global dispatch queue is pre-configured with a maximum queue size of 100 tasks. +//! +//! ```rust,ignore +//! // Ensure the dispatcher queue is being worked on. +//! dispatcher::flush_init(); +//! +//! dispatcher::launch(|| { +//! println!("Executing expensive task"); +//! // Run your expensive task in a separate thread. +//! }); +//! +//! dispatcher::launch(|| { +//! println!("A second task that's executed sequentially, but off the main thread."); +//! }); +//! ``` + +// TODO: remove this once bug 1672440 is merged and the code below +// will actually be used somewhere. +#![allow(dead_code)] + +use std::{ + mem, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread::{self, JoinHandle}, +}; + +use crossbeam_channel::{bounded, unbounded, SendError, Sender, TrySendError}; +use thiserror::Error; + +pub use global::*; + +mod global; + +/// The command a worker should execute. +enum Command { + /// A task is a user-defined function to run. + Task(Box), + + /// Swap the channel + Swap(Sender<()>), + + /// Signal the worker to finish work and shut down. + Shutdown, +} + +/// The error returned from operations on the dispatcher +#[derive(Error, Debug, PartialEq)] +pub enum DispatchError { + /// The worker panicked while running a task + #[error("The worker panicked while running a task")] + WorkerPanic, + + /// Maximum queue size reached + #[error("Maximum queue size reached")] + QueueFull, + + /// Pre-init buffer was already flushed + #[error("Pre-init buffer was already flushed")] + AlreadyFlushed, + + /// Failed to send command to worker thread + #[error("Failed to send command to worker thread")] + SendError, + + /// Failed to receive from channel + #[error("Failed to receive from channel")] + RecvError(#[from] crossbeam_channel::RecvError), +} + +impl From> for DispatchError { + fn from(err: TrySendError) -> Self { + match err { + TrySendError::Full(_) => DispatchError::QueueFull, + _ => DispatchError::SendError, + } + } +} + +impl From> for DispatchError { + fn from(_: SendError) -> Self { + DispatchError::SendError + } +} + +/// A clonable guard for a dispatch queue. +#[derive(Clone)] +struct DispatchGuard { + /// Whether to queue on the preinit buffer or on the unbounded queue + queue_preinit: Arc, + + /// Used to unblock the worker thread initially. + block_sender: Sender<()>, + + /// Sender for the preinit queue. + preinit_sender: Sender, + + /// Sender for the unbounded queue. + sender: Sender, +} + +impl DispatchGuard { + pub fn launch(&self, task: impl FnOnce() + Send + 'static) -> Result<(), DispatchError> { + let task = Command::Task(Box::new(task)); + self.send(task) + } + + pub fn shutdown(&self) -> Result<(), DispatchError> { + self.send(Command::Shutdown) + } + + fn send(&self, task: Command) -> Result<(), DispatchError> { + if self.queue_preinit.load(Ordering::SeqCst) { + match self.preinit_sender.try_send(task) { + Ok(()) => Ok(()), + Err(TrySendError::Full(_)) => Err(DispatchError::QueueFull), + Err(TrySendError::Disconnected(_)) => Err(DispatchError::SendError), + } + } else { + self.sender.send(task)?; + Ok(()) + } + } + + fn block_on_queue(&self) { + let (tx, rx) = crossbeam_channel::bounded(0); + self.launch(move || { + tx.send(()) + .expect("(worker) Can't send message on single-use channel") + }) + .expect("Failed to launch the blocking task"); + rx.recv() + .expect("Failed to receive message on single-use channel"); + } + + fn flush_init(&mut self) -> Result<(), DispatchError> { + // We immediately stop queueing in the pre-init buffer. + let old_val = self.queue_preinit.swap(false, Ordering::SeqCst); + if !old_val { + return Err(DispatchError::AlreadyFlushed); + } + + // Unblock the worker thread exactly once. + self.block_sender.send(())?; + + // Single-use channel to communicate with the worker thread. + let (swap_sender, swap_receiver) = bounded(0); + + // Send final command and block until it is sent. + self.preinit_sender + .send(Command::Swap(swap_sender)) + .map_err(|_| DispatchError::SendError)?; + + // Now wait for the worker thread to do the swap and inform us. + // This blocks until all tasks in the preinit buffer have been processed. + swap_receiver.recv()?; + Ok(()) + } +} + +/// A dispatcher. +/// +/// Run expensive processing tasks sequentially off the main thread. +/// Tasks are processed in a single separate thread in the order they are submitted. +/// The dispatch queue will enqueue tasks while not flushed, up to the maximum queue size. +/// Processing will start after flushing once, processing already enqueued tasks first, then +/// waiting for further tasks to be enqueued. +pub struct Dispatcher { + /// Guard used for communication with the worker thread. + guard: DispatchGuard, + + /// Handle to the worker thread, allows to wait for it to finish. + worker: Option>, +} + +impl Dispatcher { + /// Creates a new dispatcher with a maximum queue size. + /// + /// Launched tasks won't run until [`flush_init`] is called. + /// + /// [`flush_init`]: #method.flush_init + pub fn new(max_queue_size: usize) -> Self { + let (block_sender, block_receiver) = bounded(0); + let (preinit_sender, preinit_receiver) = bounded(max_queue_size); + let (sender, mut unbounded_receiver) = unbounded(); + + let queue_preinit = Arc::new(AtomicBool::new(true)); + + let worker = thread::spawn(move || { + if block_receiver.recv().is_err() { + // The other side was disconnected. + // There's nothing the worker thread can do. + log::error!("The task producer was disconnected. Worker thread will exit."); + return; + } + + let mut receiver = preinit_receiver; + loop { + use Command::*; + + match receiver.recv() { + Ok(Shutdown) => { + break; + } + + Ok(Task(f)) => { + (f)(); + } + + Ok(Swap(swap_done)) => { + // A swap should only occur exactly once. + // This is upheld by `flush_init`, which errors out if the preinit buffer + // was already flushed. + + // We swap the channels we listen on for new tasks. + // The next iteration will continue with the unbounded queue. + mem::swap(&mut receiver, &mut unbounded_receiver); + + // The swap command MUST be the last one received on the preinit buffer, + // so by the time we run this we know all preinit tasks were processed. + // We can notify the other side. + swap_done + .send(()) + .expect("The caller of `flush_init` has gone missing"); + } + + // Other side was disconnected. + Err(_) => { + log::error!("The task producer was disconnected. Worker thread will exit."); + return; + } + } + } + }); + + let guard = DispatchGuard { + queue_preinit, + block_sender, + preinit_sender, + sender, + }; + + Dispatcher { + guard, + worker: Some(worker), + } + } + + fn guard(&self) -> DispatchGuard { + self.guard.clone() + } + + fn block_on_queue(&self) { + self.guard().block_on_queue() + } + + /// Waits for the worker thread to finish and finishes the dispatch queue. + /// + /// You need to call `try_shutdown` to initiate a shutdown of the queue. + fn join(mut self) -> Result<(), DispatchError> { + if let Some(worker) = self.worker.take() { + worker.join().map_err(|_| DispatchError::WorkerPanic)?; + } + Ok(()) + } + + /// Flushes the pre-init buffer. + /// + /// This function blocks until tasks queued prior to this call are finished. + /// Once the initial queue is empty the dispatcher will wait for new tasks to be launched. + /// + /// Returns an error if called multiple times. + pub fn flush_init(&mut self) -> Result<(), DispatchError> { + self.guard().flush_init() + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + use std::sync::{Arc, Mutex}; + use std::{thread, time::Duration}; + + fn enable_test_logging() { + // When testing we want all logs to go to stdout/stderr by default, + // without requiring each individual test to activate it. + let _ = env_logger::builder().is_test(true).try_init(); + } + + #[test] + fn tasks_run_off_the_main_thread() { + enable_test_logging(); + + let main_thread_id = thread::current().id(); + let thread_canary = Arc::new(AtomicBool::new(false)); + + let mut dispatcher = Dispatcher::new(100); + + // Force the Dispatcher out of the pre-init queue mode. + dispatcher + .flush_init() + .expect("Failed to get out of preinit queue mode"); + + let canary_clone = thread_canary.clone(); + dispatcher + .guard() + .launch(move || { + assert!(thread::current().id() != main_thread_id); + // Use the canary bool to make sure this is getting called before + // the test completes. + assert_eq!(false, canary_clone.load(Ordering::SeqCst)); + canary_clone.store(true, Ordering::SeqCst); + }) + .expect("Failed to dispatch the test task"); + + dispatcher.block_on_queue(); + assert_eq!(true, thread_canary.load(Ordering::SeqCst)); + assert_eq!(main_thread_id, thread::current().id()); + } + + #[test] + fn launch_correctly_adds_tasks_to_preinit_queue() { + enable_test_logging(); + + let main_thread_id = thread::current().id(); + let thread_canary = Arc::new(AtomicU8::new(0)); + + let mut dispatcher = Dispatcher::new(100); + + // Add 3 tasks to queue each one increasing thread_canary by 1 to + // signal that the tasks ran. + for _ in 0..3 { + let canary_clone = thread_canary.clone(); + dispatcher + .guard() + .launch(move || { + // Make sure the task is flushed off-the-main thread. + assert!(thread::current().id() != main_thread_id); + canary_clone.fetch_add(1, Ordering::SeqCst); + }) + .expect("Failed to dispatch the test task"); + } + + // Ensure that no task ran. + assert_eq!(0, thread_canary.load(Ordering::SeqCst)); + + // Flush the queue and wait for the tasks to complete. + dispatcher + .flush_init() + .expect("Failed to get out of preinit queue mode"); + // Validate that we have the expected canary value. + assert_eq!(3, thread_canary.load(Ordering::SeqCst)); + } + + #[test] + fn preinit_tasks_are_processed_after_flush() { + enable_test_logging(); + + let mut dispatcher = Dispatcher::new(10); + + let result = Arc::new(Mutex::new(vec![])); + for i in 1..=5 { + let result = Arc::clone(&result); + dispatcher + .guard() + .launch(move || { + result.lock().unwrap().push(i); + }) + .unwrap(); + } + + result.lock().unwrap().push(0); + dispatcher.flush_init().unwrap(); + for i in 6..=10 { + let result = Arc::clone(&result); + dispatcher + .guard() + .launch(move || { + result.lock().unwrap().push(i); + }) + .unwrap(); + } + + dispatcher.block_on_queue(); + + // This additionally checks that tasks were executed in order. + assert_eq!( + &*result.lock().unwrap(), + &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ); + } + + #[test] + fn tasks_after_shutdown_are_not_processed() { + enable_test_logging(); + + let mut dispatcher = Dispatcher::new(10); + + let result = Arc::new(Mutex::new(vec![])); + + dispatcher.flush_init().unwrap(); + + dispatcher.guard().shutdown().unwrap(); + { + let result = Arc::clone(&result); + // This might fail because the shutdown is quick enough, + // or it might succeed and still send the task. + // In any case that task should not be executed. + let _ = dispatcher.guard().launch(move || { + result.lock().unwrap().push(0); + }); + } + + dispatcher.join().unwrap(); + + assert!(result.lock().unwrap().is_empty()); + } + + #[test] + fn preinit_buffer_fills_up() { + enable_test_logging(); + + let mut dispatcher = Dispatcher::new(5); + + let result = Arc::new(Mutex::new(vec![])); + + for i in 1..=5 { + let result = Arc::clone(&result); + dispatcher + .guard() + .launch(move || { + result.lock().unwrap().push(i); + }) + .unwrap(); + } + + { + let result = Arc::clone(&result); + let err = dispatcher.guard().launch(move || { + result.lock().unwrap().push(10); + }); + assert_eq!(Err(DispatchError::QueueFull), err); + } + + dispatcher.flush_init().unwrap(); + + { + let result = Arc::clone(&result); + dispatcher + .guard() + .launch(move || { + result.lock().unwrap().push(20); + }) + .unwrap(); + } + + dispatcher.block_on_queue(); + + assert_eq!(&*result.lock().unwrap(), &[1, 2, 3, 4, 5, 20]); + } + + #[test] + fn normal_queue_is_unbounded() { + enable_test_logging(); + + // Note: We can't actually test that it's fully unbounded, + // but we can quickly queue more slow tasks than the pre-init buffer holds + // and then guarantuee they all run. + + let mut dispatcher = Dispatcher::new(5); + + let result = Arc::new(Mutex::new(vec![])); + + for i in 1..=5 { + let result = Arc::clone(&result); + dispatcher + .guard() + .launch(move || { + result.lock().unwrap().push(i); + }) + .unwrap(); + } + + dispatcher.flush_init().unwrap(); + + // Queue more than 5 tasks, + // Each one is slow to process, so we should be faster in queueing + // them up than they are processed. + for i in 6..=20 { + let result = Arc::clone(&result); + dispatcher + .guard() + .launch(move || { + thread::sleep(Duration::from_millis(50)); + result.lock().unwrap().push(i); + }) + .unwrap(); + } + + dispatcher.guard().shutdown().unwrap(); + dispatcher.join().unwrap(); + + let expected = (1..=20).collect::>(); + assert_eq!(&*result.lock().unwrap(), &expected); + } +} diff --git a/third_party/rust/glean/src/glean_metrics.rs b/third_party/rust/glean/src/glean_metrics.rs new file mode 100644 index 000000000000..782f04d61075 --- /dev/null +++ b/third_party/rust/glean/src/glean_metrics.rs @@ -0,0 +1,10 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// ** IMPORTANT ** +// +// This file is required in order to include the ones generated by +// 'glean-parser' from the SDK registry files. + +include!(concat!("pings.rs")); diff --git a/third_party/rust/glean/src/lib.rs b/third_party/rust/glean/src/lib.rs new file mode 100644 index 000000000000..d74459a5fa02 --- /dev/null +++ b/third_party/rust/glean/src/lib.rs @@ -0,0 +1,337 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#![deny(missing_docs)] + +//! Glean is a modern approach for recording and sending Telemetry data. +//! +//! It's in use at Mozilla. +//! +//! All documentation can be found online: +//! +//! ## [The Glean SDK Book](https://mozilla.github.io/glean) +//! +//! ## Example +//! +//! Initialize Glean, register a ping and then send it. +//! +//! ```rust,no_run +//! # use glean::{Configuration, ClientInfoMetrics, Error, private::*}; +//! let cfg = Configuration { +//! data_path: "/tmp/data".into(), +//! application_id: "org.mozilla.glean_core.example".into(), +//! upload_enabled: true, +//! max_events: None, +//! delay_ping_lifetime_io: false, +//! channel: None, +//! }; +//! glean::initialize(cfg, ClientInfoMetrics::unknown()); +//! +//! let prototype_ping = PingType::new("prototype", true, true, vec!()); +//! +//! glean::register_ping_type(&prototype_ping); +//! +//! prototype_ping.submit(None); +//! ``` + +use once_cell::sync::OnceCell; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Mutex; + +pub use configuration::Configuration; +pub use core_metrics::ClientInfoMetrics; +pub use glean_core::{global_glean, setup_glean, CommonMetricData, Error, Glean, Lifetime, Result}; + +mod configuration; +mod core_metrics; +pub mod dispatcher; +mod glean_metrics; +pub mod private; +mod system; + +const LANGUAGE_BINDING_NAME: &str = "Rust"; + +/// State to keep track for the Rust Language bindings. +/// +/// This is useful for setting Glean SDK-owned metrics when +/// the state of the upload is toggled. +#[derive(Debug)] +struct RustBindingsState { + /// The channel the application is being distributed on. + channel: Option, + + /// Client info metrics set by the application. + client_info: ClientInfoMetrics, +} + +/// Set when `glean::initialize()` returns. +/// This allows to detect calls that happen before `glean::initialize()` was called. +/// Note: The initialization might still be in progress, as it runs in a separate thread. +static INITIALIZE_CALLED: AtomicBool = AtomicBool::new(false); + +/// A global singleton storing additional state for Glean. +/// +/// Requires a Mutex, because in tests we can actual reset this. +static STATE: OnceCell> = OnceCell::new(); + +/// Get a reference to the global state object. +/// +/// Panics if no global state object was set. +fn global_state() -> &'static Mutex { + STATE.get().unwrap() +} + +/// Set or replace the global Glean object. +fn setup_state(state: RustBindingsState) { + if STATE.get().is_none() { + STATE.set(Mutex::new(state)).unwrap(); + } else { + let mut lock = STATE.get().unwrap().lock().unwrap(); + *lock = state; + } +} + +fn with_glean(f: F) -> R +where + F: Fn(&Glean) -> R, +{ + let glean = global_glean().expect("Global Glean object not initialized"); + let lock = glean.lock().unwrap(); + f(&lock) +} + +fn with_glean_mut(f: F) -> R +where + F: Fn(&mut Glean) -> R, +{ + let glean = global_glean().expect("Global Glean object not initialized"); + let mut lock = glean.lock().unwrap(); + f(&mut lock) +} + +/// Creates and initializes a new Glean object. +/// +/// See `glean_core::Glean::new` for more information. +/// +/// # Arguments +/// +/// * `cfg` - the `Configuration` options to initialize with. +/// * `client_info` - the `ClientInfoMetrics` values used to set Glean +/// core metrics. +pub fn initialize(cfg: Configuration, client_info: ClientInfoMetrics) { + if was_initialize_called() { + log::error!("Glean should not be initialized multiple times"); + return; + } + + std::thread::spawn(move || { + let core_cfg = glean_core::Configuration { + upload_enabled: cfg.upload_enabled, + data_path: cfg.data_path.clone(), + application_id: cfg.application_id.clone(), + language_binding_name: LANGUAGE_BINDING_NAME.into(), + max_events: cfg.max_events, + delay_ping_lifetime_io: cfg.delay_ping_lifetime_io, + }; + + let glean = match Glean::new(core_cfg) { + Ok(glean) => glean, + // glean-core already takes care of logging errors: other bindings + // simply do early returns, as we're doing. + Err(_) => return, + }; + + // glean-core already takes care of logging errors: other bindings + // simply do early returns, as we're doing. + if glean_core::setup_glean(glean).is_err() { + return; + } + + log::info!("Glean initialized"); + + // Now make this the global object available to others. + setup_state(RustBindingsState { + channel: cfg.channel, + client_info, + }); + + let upload_enabled = cfg.upload_enabled; + + with_glean_mut(|glean| { + let state = global_state().lock().unwrap(); + + // Get the current value of the dirty flag so we know whether to + // send a dirty startup baseline ping below. Immediately set it to + // `false` so that dirty startup pings won't be sent if Glean + // initialization does not complete successfully. + // TODO Bug 1672956 will decide where to set this flag again. + let dirty_flag = glean.is_dirty_flag_set(); + glean.set_dirty_flag(false); + + // Register builtin pings. + // Unfortunately we need to manually list them here to guarantee + // they are registered synchronously before we need them. + // We don't need to handle the deletion-request ping. It's never touched + // from the language implementation. + glean.register_ping_type(&glean_metrics::pings::baseline.ping_type); + glean.register_ping_type(&glean_metrics::pings::metrics.ping_type); + glean.register_ping_type(&glean_metrics::pings::events.ping_type); + + // TODO: perform registration of pings that were attempted to be + // registered before init. See bug 1673850. + + // If this is the first time ever the Glean SDK runs, make sure to set + // some initial core metrics in case we need to generate early pings. + // The next times we start, we would have them around already. + let is_first_run = glean.is_first_run(); + if is_first_run { + initialize_core_metrics(&glean, &state.client_info, state.channel.clone()); + } + + // Deal with any pending events so we can start recording new ones + let pings_submitted = glean.on_ready_to_submit_pings(); + + // We need to kick off upload in these cases: + // 1. Pings were submitted through Glean and it is ready to upload those pings; + // 2. Upload is disabled, to upload a possible deletion-request ping. + if pings_submitted || !upload_enabled { + // TODO: bug 1672958. + } + + // Set up information and scheduling for Glean owned pings. Ideally, the "metrics" + // ping startup check should be performed before any other ping, since it relies + // on being dispatched to the API context before any other metric. + // TODO: start the metrics ping scheduler, will happen in bug 1672951. + + // Check if the "dirty flag" is set. That means the product was probably + // force-closed. If that's the case, submit a 'baseline' ping with the + // reason "dirty_startup". We only do that from the second run. + if !is_first_run && dirty_flag { + // TODO: bug 1672958 - submit_ping_by_name_sync("baseline", "dirty_startup"); + } + + // From the second time we run, after all startup pings are generated, + // make sure to clear `lifetime: application` metrics and set them again. + // Any new value will be sent in newly generated pings after startup. + if !is_first_run { + glean.clear_application_lifetime_metrics(); + initialize_core_metrics(&glean, &state.client_info, state.channel.clone()); + } + }); + + // Signal Dispatcher that init is complete + if let Err(err) = dispatcher::flush_init() { + log::error!("Unable to flush the preinit queue: {}", err); + } + }); + + // Mark the initialization as called: this needs to happen outside of the + // dispatched block! + INITIALIZE_CALLED.store(true, Ordering::SeqCst); +} + +/// Checks if `glean::initialize` was ever called. +/// +/// # Returns +/// +/// `true` if it was, `false` otherwise. +fn was_initialize_called() -> bool { + INITIALIZE_CALLED.load(Ordering::SeqCst) +} + +fn initialize_core_metrics( + glean: &Glean, + client_info: &ClientInfoMetrics, + channel: Option, +) { + let core_metrics = core_metrics::InternalMetrics::new(); + + core_metrics + .app_build + .set(glean, &client_info.app_build[..]); + core_metrics + .app_display_version + .set(glean, &client_info.app_display_version[..]); + if let Some(app_channel) = channel { + core_metrics.app_channel.set(glean, app_channel); + } + core_metrics.os_version.set(glean, "unknown".to_string()); + core_metrics + .architecture + .set(glean, system::ARCH.to_string()); + core_metrics + .device_manufacturer + .set(glean, "unknown".to_string()); + core_metrics.device_model.set(glean, "unknown".to_string()); +} + +/// Sets whether upload is enabled or not. +/// +/// See `glean_core::Glean.set_upload_enabled`. +pub fn set_upload_enabled(enabled: bool) { + if !was_initialize_called() { + let msg = + "Changing upload enabled before Glean is initialized is not supported.\n \ + Pass the correct state into `Glean.initialize()`.\n \ + See documentation at https://mozilla.github.io/glean/book/user/general-api.html#initializing-the-glean-sdk"; + log::error!("{}", msg); + return; + } + + // Changing upload enabled always happens asynchronous. + // That way it follows what a user expect when calling it inbetween other calls: + // it executes in the right order. + // + // Because the dispatch queue is halted until Glean is fully initialized + // we can safely enqueue here and it will execute after initialization. + dispatcher::launch(move || { + with_glean_mut(|glean| { + let state = global_state().lock().unwrap(); + let old_enabled = glean.is_upload_enabled(); + glean.set_upload_enabled(enabled); + + // TODO: Cancel upload and any outstanding metrics ping scheduler + // task. Will happen on bug 1672951. + + if !old_enabled && enabled { + // If uploading is being re-enabled, we have to restore the + // application-lifetime metrics. + initialize_core_metrics(&glean, &state.client_info, state.channel.clone()); + } + + // TODO: trigger upload for the deletion-ping. Will happen in bug 1672952. + }); + }); +} + +/// Register a new [`PingType`](metrics/struct.PingType.html). +pub fn register_ping_type(ping: &private::PingType) { + let ping = ping.clone(); + dispatcher::launch(move || { + with_glean_mut(|glean| { + glean.register_ping_type(&ping.ping_type); + }) + }) +} + +/// Collects and submits a ping for eventual uploading. +/// +/// See `glean_core::Glean.submit_ping`. +pub fn submit_ping(ping: &private::PingType, reason: Option<&str>) { + submit_ping_by_name(&ping.name, reason) +} + +/// Collects and submits a ping for eventual uploading by name. +/// +/// See `glean_core::Glean.submit_ping_by_name`. +pub fn submit_ping_by_name(ping: &str, reason: Option<&str>) { + let ping = ping.to_string(); + let reason = reason.map(|s| s.to_string()); + dispatcher::launch(move || { + with_glean(|glean| glean.submit_ping_by_name(&ping, reason.as_deref()).ok()); + }) +} + +#[cfg(test)] +mod test; diff --git a/third_party/rust/glean/src/pings.rs b/third_party/rust/glean/src/pings.rs new file mode 100644 index 000000000000..68e2071bff70 --- /dev/null +++ b/third_party/rust/glean/src/pings.rs @@ -0,0 +1,57 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// ** IMPORTANT ** +// +// This file is *temporary*, it will be generated by 'glean-parser' +// from the SDK registry files in the long run. + +pub mod pings { + use crate::private::PingType; + use once_cell::sync::Lazy; + + #[allow(non_upper_case_globals)] + pub static baseline: Lazy = Lazy::new(|| { + PingType::new( + "baseline", + true, + true, + vec![ + "background".to_string(), + "dirty_startup".to_string(), + "foreground".to_string() + ] + ) + }); + + #[allow(non_upper_case_globals)] + pub static metrics: Lazy = Lazy::new(|| { + PingType::new( + "metrics", + true, + false, + vec![ + "overdue".to_string(), + "reschedule".to_string(), + "today".to_string(), + "tomorrow".to_string(), + "upgrade".to_string() + ] + ) + }); + + #[allow(non_upper_case_globals)] + pub static events: Lazy = Lazy::new(|| { + PingType::new( + "metrics", + true, + false, + vec![ + "background".to_string(), + "max_capacity".to_string(), + "startup".to_string() + ] + ) + }); +} diff --git a/third_party/rust/glean/src/private/boolean.rs b/third_party/rust/glean/src/private/boolean.rs new file mode 100644 index 000000000000..a6376f27f2b2 --- /dev/null +++ b/third_party/rust/glean/src/private/boolean.rs @@ -0,0 +1,64 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use inherent::inherent; +use std::sync::Arc; + +use glean_core::metrics::MetricType; + +use crate::dispatcher; + +// We need to wrap the glean-core type: otherwise if we try to implement +// the trait for the metric in `glean_core::metrics` we hit error[E0117]: +// only traits defined in the current crate can be implemented for arbitrary +// types. + +/// This implements the developer facing API for recording boolean metrics. +/// +/// Instances of this class type are automatically generated by the parsers +/// at build time, allowing developers to record values that were previously +/// registered in the metrics.yaml file. +#[derive(Clone)] +pub struct BooleanMetric(pub(crate) Arc); + +impl BooleanMetric { + /// The public constructor used by automatically generated metrics. + pub fn new(meta: glean_core::CommonMetricData) -> Self { + Self(Arc::new(glean_core::metrics::BooleanMetric::new(meta))) + } +} + +#[inherent(pub)] +impl glean_core::traits::Boolean for BooleanMetric { + /// Sets to the specified boolean value. + /// + /// # Arguments + /// + /// * `value` - the value to set. + fn set(&self, value: bool) { + let metric = Arc::clone(&self.0); + dispatcher::launch(move || crate::with_glean(|glean| metric.set(glean, value))); + } + + /// **Exported for test purposes.** + /// + /// Gets the currently stored value as a boolean. + /// + /// This doesn't clear the stored value. + /// + /// # Arguments + /// + /// * `ping_name` - represents the optional name of the ping to retrieve the + /// metric for. Defaults to the first value in `send_in_pings`. + fn test_get_value<'a, S: Into>>(&self, ping_name: S) -> Option { + dispatcher::block_on_queue(); + + let queried_ping_name = match ping_name.into() { + Some(name) => name, + None => self.0.meta().send_in_pings.first().unwrap(), + }; + + crate::with_glean(|glean| self.0.test_get_value(glean, queried_ping_name)) + } +} diff --git a/third_party/rust/glean/src/private/mod.rs b/third_party/rust/glean/src/private/mod.rs new file mode 100644 index 000000000000..36a31075f8ad --- /dev/null +++ b/third_party/rust/glean/src/private/mod.rs @@ -0,0 +1,11 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +//! The different metric types supported by the Glean SDK to handle data. + +mod boolean; +mod ping; + +pub use boolean::BooleanMetric; +pub use ping::PingType; diff --git a/third_party/rust/glean/src/private/ping.rs b/third_party/rust/glean/src/private/ping.rs new file mode 100644 index 000000000000..c30b66dd1935 --- /dev/null +++ b/third_party/rust/glean/src/private/ping.rs @@ -0,0 +1,44 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +/// Stores information about a ping. +/// +/// This is required so that given metric data queued on disk we can send +/// pings with the correct settings, e.g. whether it has a client_id. +#[derive(Clone, Debug)] +pub struct PingType { + pub(crate) name: String, + pub(crate) ping_type: glean_core::metrics::PingType, +} + +impl PingType { + /// Creates a new ping type. + /// + /// # Arguments + /// + /// * `name` - The name of the ping. + /// * `include_client_id` - Whether to include the client ID in the assembled ping when. + /// * `send_if_empty` - Whether the ping should be sent empty or not. + /// * `reason_codes` - The valid reason codes for this ping. + pub fn new>( + name: A, + include_client_id: bool, + send_if_empty: bool, + reason_codes: Vec, + ) -> Self { + let name = name.into(); + let ping_type = glean_core::metrics::PingType::new( + name.clone(), + include_client_id, + send_if_empty, + reason_codes, + ); + Self { name, ping_type } + } + + /// Submits the ping. + pub fn submit(&self, reason: Option<&str>) { + crate::submit_ping(self, reason) + } +} diff --git a/third_party/rust/glean/src/system.rs b/third_party/rust/glean/src/system.rs new file mode 100644 index 000000000000..5bb7d3c34ac7 --- /dev/null +++ b/third_party/rust/glean/src/system.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2017 The Rust Project Developers +// Licensed under the MIT License. +// Original license: +// https://github.com/RustSec/platforms-crate/blob/ebbd3403243067ba3096f31684557285e352b639/LICENSE-MIT +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Detect and expose `target_arch` as a constant + +#[cfg(target_arch = "aarch64")] +/// `target_arch` when building this crate: `aarch64` +pub const ARCH: &str = "aarch64"; + +#[cfg(target_arch = "arm")] +/// `target_arch` when building this crate: `arm` +pub const ARCH: &str = "arm"; + +#[cfg(target_arch = "x86")] +/// `target_arch` when building this crate: `x86` +pub const ARCH: &str = "x86"; + +#[cfg(target_arch = "x86_64")] +/// `target_arch` when building this crate: `x86_64` +pub const ARCH: &str = "x86_64"; + +#[cfg(not(any( + target_arch = "aarch64", + target_arch = "arm", + target_arch = "x86", + target_arch = "x86_64" +)))] +/// `target_arch` when building this crate: unknown! +pub const ARCH: &str = "unknown"; diff --git a/third_party/rust/glean/src/test.rs b/third_party/rust/glean/src/test.rs new file mode 100644 index 000000000000..917cf6863268 --- /dev/null +++ b/third_party/rust/glean/src/test.rs @@ -0,0 +1,201 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use crate::private::BooleanMetric; +use once_cell::sync::Lazy; +use std::path::PathBuf; +use std::sync::Mutex; + +use super::*; + +// Because glean_preview is a global-singleton, we need to run the tests one-by-one to avoid different tests stomping over each other. +// This is only an issue because we're resetting Glean, this cannot happen in normal use of the +// RLB. +// +// We use a global lock to force synchronization of all tests, even if run multi-threaded. +// This allows us to run without `--test-threads 1`.` +static GLOBAL_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); +const GLOBAL_APPLICATION_ID: &str = "org.mozilla.rlb.test"; + +// Create a new instance of Glean with a temporary directory. +// We need to keep the `TempDir` alive, so that it's not deleted before we stop using it. +fn new_glean() -> tempfile::TempDir { + let dir = tempfile::tempdir().unwrap(); + let tmpname = dir.path().display().to_string(); + + let cfg = Configuration { + data_path: tmpname, + application_id: GLOBAL_APPLICATION_ID.into(), + upload_enabled: true, + max_events: None, + delay_ping_lifetime_io: false, + channel: Some("testing".into()), + }; + + initialize(cfg, ClientInfoMetrics::unknown()); + dir +} + +#[test] +fn disabling_upload_disables_metrics_recording() { + let _lock = GLOBAL_LOCK.lock().unwrap(); + env_logger::try_init().ok(); + + let _t = new_glean(); + crate::dispatcher::block_on_queue(); + + let metric = BooleanMetric::new(CommonMetricData { + name: "bool_metric".into(), + category: "test".into(), + send_in_pings: vec!["store1".into()], + lifetime: Lifetime::Application, + disabled: false, + dynamic_label: None, + }); + + crate::set_upload_enabled(false); + + assert!(metric.test_get_value("store1").is_none()) +} + +#[test] +#[ignore] // TODO: To be done in bug 1672982. +fn test_experiments_recording() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1672982. +fn test_experiments_recording_before_glean_inits() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673645. +fn test_sending_of_foreground_background_pings() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1672958. +fn test_sending_of_startup_baseline_ping() { + todo!() +} + +#[test] +fn initialize_must_not_crash_if_data_dir_is_messed_up() { + let _lock = GLOBAL_LOCK.lock().unwrap(); + env_logger::try_init().ok(); + + let dir = tempfile::tempdir().unwrap(); + let tmpdirname = dir.path().display().to_string(); + // Create a file in the temporary dir and use that as the + // name of the Glean data dir. + let file_path = PathBuf::from(tmpdirname).join("notadir"); + std::fs::write(file_path.clone(), "test").expect("The test Glean dir file must be created"); + + let cfg = Configuration { + data_path: file_path.to_string_lossy().to_string(), + application_id: GLOBAL_APPLICATION_ID.into(), + upload_enabled: true, + max_events: None, + delay_ping_lifetime_io: false, + channel: Some("testing".into()), + }; + + initialize(cfg, ClientInfoMetrics::unknown()); + + // TODO: is this test working? is it waiting for init to finish? + dispatcher::block_on_queue(); +} + +#[test] +#[ignore] // TODO: To be done in bug 1673667. +fn queued_recorded_metrics_correctly_record_during_init() { + todo!() +} + +#[test] +fn initializing_twice_is_a_noop() { + let dir = tempfile::tempdir().unwrap(); + let tmpname = dir.path().display().to_string(); + + let cfg = Configuration { + data_path: tmpname, + application_id: GLOBAL_APPLICATION_ID.into(), + upload_enabled: true, + max_events: None, + delay_ping_lifetime_io: false, + channel: Some("testing".into()), + }; + + initialize(cfg.clone(), ClientInfoMetrics::unknown()); + initialize(cfg, ClientInfoMetrics::unknown()); +} + +#[test] +#[ignore] // TODO: To be done in bug 1673668. +fn dont_handle_events_when_uninitialized() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn the_app_channel_must_be_correctly_set_if_requested() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn ping_collection_must_happen_after_concurrently_scheduled_metrics_recordings() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn basic_metrics_should_be_cleared_when_disabling_uploading() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn core_metrics_should_be_cleared_and_restored_when_disabling_and_enabling_uploading() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn overflowing_the_task_queue_records_telemetry() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn sending_deletion_ping_if_disabled_outside_of_run() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn no_sending_of_deletion_ping_if_unchanged_outside_of_run() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn test_sending_of_startup_baseline_ping_with_application_lifetime_metric() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn test_dirty_flag_is_reset_to_false() { + todo!() +} + +#[test] +#[ignore] // TODO: To be done in bug 1673672. +fn flipping_upload_enabled_respects_order_of_events() { + todo!() +} diff --git a/third_party/rust/glean/tests/schema.rs b/third_party/rust/glean/tests/schema.rs new file mode 100644 index 000000000000..6dae08e2071d --- /dev/null +++ b/third_party/rust/glean/tests/schema.rs @@ -0,0 +1,93 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; + +use jsonschema_valid::{self, schemas::Draft}; +use serde_json::Value; + +use glean::{private::PingType, ClientInfoMetrics, Configuration}; + +const SCHEMA_JSON: &str = include_str!("../../../glean.1.schema.json"); + +fn load_schema() -> Value { + serde_json::from_str(SCHEMA_JSON).unwrap() +} + +const GLOBAL_APPLICATION_ID: &str = "org.mozilla.glean.test.app"; + +// Create a new instance of Glean with a temporary directory. +// We need to keep the `TempDir` alive, so that it's not deleted before we stop using it. +fn new_glean() -> tempfile::TempDir { + let dir = tempfile::tempdir().unwrap(); + let tmpname = dir.path().display().to_string(); + + let cfg = Configuration { + data_path: tmpname, + application_id: GLOBAL_APPLICATION_ID.into(), + upload_enabled: true, + max_events: None, + delay_ping_lifetime_io: false, + channel: None, + }; + + let client_info = ClientInfoMetrics { + app_build: env!("CARGO_PKG_VERSION").to_string(), + app_display_version: env!("CARGO_PKG_VERSION").to_string(), + }; + + glean::initialize(cfg, client_info); + + dir +} + +#[test] +fn validate_against_schema() { + let schema = load_schema(); + + let dir = new_glean(); + + // Register and submit a ping for testing + let ping_type = PingType::new("test", true, /* send_if_empty */ true, vec![]); + glean::register_ping_type(&ping_type); + ping_type.submit(None); + glean::dispatcher::block_on_queue(); + + // Read the ping from disk. + // We know where it should be placed. + let pings_dir = dir.path().join("pending_pings"); + let pending_pings: Vec = pings_dir + .read_dir() + .unwrap() + .filter_map(|entry| entry.ok()) + .filter_map(|entry| match entry.file_type() { + Ok(f) if f.is_file() => Some(entry.path()), + _ => None, + }) + .collect(); + + // There should only be one: our custom ping. + assert_eq!(1, pending_pings.len()); + + // 1. line: the URL + // 2. line: the JSON body + let file = File::open(&pending_pings[0]).unwrap(); + let mut lines = BufReader::new(file).lines(); + let _url = lines.next().unwrap().unwrap(); + let body = lines.next().unwrap().unwrap(); + + // Now validate against the vendored schema + let data = serde_json::from_str(&body).unwrap(); + let cfg = jsonschema_valid::Config::from_schema(&schema, Some(Draft::Draft6)).unwrap(); + let validation = cfg.validate(&data); + match validation { + Ok(()) => {} + Err(e) => { + let errors = e.map(|e| format!("{}", e)).collect::>(); + assert!(false, "Data: {:#?}\nErrors: {:#?}", data, errors); + } + } +} diff --git a/third_party/rust/inherent/.cargo-checksum.json b/third_party/rust/inherent/.cargo-checksum.json new file mode 100644 index 000000000000..2c68983db68e --- /dev/null +++ b/third_party/rust/inherent/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"0ce5ab889ad4a341e85972527435c959bc54d2f3732fa67b7b13d7627e6fba21","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"74bd3fad47365c6947c8026d71c4447503eadc4e59cab5b99008758764ca8163","src/default_methods.rs":"71d2d0e6a54f30b5958ecaa48b49fd21560620f98e468a99142bea1d954b36a4","src/expand.rs":"eace0ba684a4b002a995906565882e2b7753be6642bbe01c6ada30767730a624","src/lib.rs":"6e844f4e0104c942945e8071b346788f01f5e63e15d378b501b966d10d49f8ed","src/parse.rs":"5478cff1e20ac6dc2c95772eaecfa8b8ec557effe689b536d865287c0b0b89ce","src/visibility.rs":"d5334b8db4953bd7aa9277e75717f2bdaf4ceb01e98db101d040687238f22e20","tests/compiletest.rs":"0a52a44786aea1c299c695bf948b2ed2081e4cc344e5c2cadceab4eb03d0010d","tests/test.rs":"5ba0edc93c0edf63744d45cd293ed8c522ee3a7cb9da3df4d7dacaa6ed004478","tests/ui/default-with-body.rs":"d7a6ad44e8327f298b88e93b43adb019d4278ccd2f56f0ca98c819d08bf8906a","tests/ui/default-with-body.stderr":"e3669c86597c7146dba36f3dfa6a200fd66e315b9e5518171c1ec45c883e1e2d","tests/ui/nonsense-in-default.rs":"694f0e75e213ad78ad30124f00bb45517331f2eceb8f44f4f49d5f5d917ba018","tests/ui/nonsense-in-default.stderr":"96d47e0b3b00197e6f959759a8c57a77d7af68ebfa8d0ed1d4df3f6fe0995717","tests/ui/not-trait-impl.rs":"e99aba255dc75c498d43098c496a2b349631de74ca51b6fdcd90fc744a4fcc94","tests/ui/not-trait-impl.stderr":"12e936203b335bccc3a180c66523c0d434fac6038521d1a6712ee5d37a7eec4d","tests/ui/not-visible.rs":"ee9c08bf412b0ce2b077ab53a2ad4c98ada104c48721b484bfe79ebb2bc36230","tests/ui/not-visible.stderr":"c8bf2a1efbe851a7b1eb1490ea621826f37670bf80d17090b0437140cc396995"},"package":"c2db418475edf9eb55fec92b7527be6d9a7b880bff6651cb0c0345af57f1cadf"} \ No newline at end of file diff --git a/third_party/rust/inherent/Cargo.toml b/third_party/rust/inherent/Cargo.toml new file mode 100644 index 000000000000..92f9bc973765 --- /dev/null +++ b/third_party/rust/inherent/Cargo.toml @@ -0,0 +1,42 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "inherent" +version = "0.1.4" +authors = ["David Tolnay "] +description = "Make trait methods callable without the trait in scope" +documentation = "https://docs.rs/inherent" +readme = "README.md" +license = "MIT OR Apache-2.0" +repository = "https://github.com/dtolnay/inherent" +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[lib] +proc-macro = true +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "1.0" +features = ["full"] +[dev-dependencies.rustversion] +version = "1.0" + +[dev-dependencies.trybuild] +version = "1.0.19" +features = ["diff"] diff --git a/third_party/rust/inherent/LICENSE-APACHE b/third_party/rust/inherent/LICENSE-APACHE new file mode 100644 index 000000000000..16fe87b06e80 --- /dev/null +++ b/third_party/rust/inherent/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/third_party/rust/inherent/LICENSE-MIT b/third_party/rust/inherent/LICENSE-MIT new file mode 100644 index 000000000000..31aa79387f27 --- /dev/null +++ b/third_party/rust/inherent/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/third_party/rust/inherent/README.md b/third_party/rust/inherent/README.md new file mode 100644 index 000000000000..102abab63e3f --- /dev/null +++ b/third_party/rust/inherent/README.md @@ -0,0 +1,104 @@ +\#\[inherent\] +============== + +[github](https://github.com/dtolnay/inherent) +[crates.io](https://crates.io/crates/inherent) +[docs.rs](https://docs.rs/inherent) +[build status](https://github.com/dtolnay/inherent/actions?query=branch%3Amaster) + +This crate provides an attribute macro to make trait methods callable without +the trait in scope. + +```toml +[dependencies] +inherent = "0.1" +``` + +## Example + +```rust +mod types { + use inherent::inherent; + + trait Trait { + fn f(self); + } + + pub struct Struct; + + #[inherent(pub)] + impl Trait for Struct { + fn f(self) {} + } +} + +fn main() { + // types::Trait is not in scope, but method can be called. + types::Struct.f(); +} +``` + +Without the `inherent` macro on the trait impl, this would have failed with the +following error: + +```console +error[E0599]: no method named `f` found for type `types::Struct` in the current scope + --> src/main.rs:18:19 + | +8 | pub struct Struct; + | ------------------ method `f` not found for this +... +18 | types::Struct.f(); + | ^ + | + = help: items from traits can only be used if the trait is implemented and in scope + = note: the following trait defines an item `f`, perhaps you need to implement it: + candidate #1: `types::Trait` +``` + +The `inherent` macro expands to inherent methods on the `Self` type of the trait +impl that forward to the trait methods. In the case above, the generated code +would be: + +```rust +impl Struct { + pub fn f(self) { + ::f(self) + } +} +``` + +## Visibility + +Ordinary trait methods have the same visibility as the trait or the `Self` type, +whichever's is smaller. Neither of these visibilities is knowable to the +`inherent` macro, so we simply emit all inherent methods as private by default. +A different visibility may be specified explicitly in the `inherent` macro +invocation. + +```rust +#[inherent] // private inherent methods are the default + +#[inherent(pub)] // all methods pub + +#[inherent(crate)] // all methods pub(crate) + +#[inherent(in path::to)] // all methods pub(in path::to) +``` + +
+ +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + diff --git a/third_party/rust/inherent/src/default_methods.rs b/third_party/rust/inherent/src/default_methods.rs new file mode 100644 index 000000000000..9415a48a7123 --- /dev/null +++ b/third_party/rust/inherent/src/default_methods.rs @@ -0,0 +1,23 @@ +use syn::parse::{Error, ParseStream, Result}; +use syn::TraitItemMethod; + +pub fn parse(input: ParseStream) -> Result> { + let mut items = Vec::new(); + let mut error = None; + while !input.is_empty() { + let item = input.parse::()?; + if let Some(body) = item.default { + let new_err = Error::new_spanned(body, "Default method can't have a body"); + match &mut error { + None => error = Some(new_err), + Some(e) => e.combine(new_err), + } + } else { + items.push(item); + } + } + match error { + None => Ok(items), + Some(err) => Err(err), + } +} diff --git a/third_party/rust/inherent/src/expand.rs b/third_party/rust/inherent/src/expand.rs new file mode 100644 index 000000000000..631a2a24e9a2 --- /dev/null +++ b/third_party/rust/inherent/src/expand.rs @@ -0,0 +1,101 @@ +use crate::default_methods; +use crate::parse::TraitImpl; +use crate::visibility::Visibility; +use proc_macro2::{Span, TokenStream}; +use quote::quote; +use syn::{Attribute, FnArg, Ident, ImplItem, Signature}; + +pub fn inherent(vis: Visibility, mut input: TraitImpl) -> TokenStream { + let generics = &input.generics; + let where_clause = &input.generics.where_clause; + let trait_ = &input.trait_; + let ty = &input.self_ty; + + let fwd_method = |attrs: &[Attribute], sig: &Signature| { + let constness = &sig.constness; + let asyncness = &sig.asyncness; + let unsafety = &sig.unsafety; + let abi = &sig.abi; + let ident = &sig.ident; + let generics = &sig.generics; + let output = &sig.output; + let where_clause = &sig.generics.where_clause; + + let (arg_pat, arg_val): (Vec<_>, Vec<_>) = sig + .inputs + .iter() + .enumerate() + .map(|(i, input)| match input { + FnArg::Receiver(receiver) => { + if receiver.reference.is_some() { + (quote!(#receiver), quote!(self)) + } else { + (quote!(self), quote!(self)) + } + } + FnArg::Typed(arg) => { + let var = Ident::new(&format!("__arg{}", i), Span::call_site()); + let ty = &arg.ty; + (quote!(#var: #ty), quote!(#var)) + } + }) + .unzip(); + + let types = generics.type_params().map(|param| ¶m.ident); + + let has_doc = attrs.iter().any(|attr| attr.path.is_ident("doc")); + let default_doc = if has_doc { + None + } else { + let msg = format!("See [{}::{}]", quote!(#trait_), ident); + Some(quote!(#[doc = #msg])) + }; + + quote! { + #(#attrs)* + #default_doc + #vis #constness #asyncness #unsafety #abi fn #ident #generics ( + #(#arg_pat,)* + ) #output #where_clause { + ::#ident::<#(#types,)*>(#(#arg_val,)*) + } + } + }; + + let mut errors = Vec::new(); + let fwd_methods: Vec<_> = input + .items + .iter() + .flat_map(|item| match item { + ImplItem::Method(method) => vec![fwd_method(&method.attrs, &method.sig)], + ImplItem::Macro(item) if item.mac.path.is_ident("default") => { + match item.mac.parse_body_with(default_methods::parse) { + Ok(body) => body + .into_iter() + .map(|item| fwd_method(&item.attrs, &item.sig)) + .collect(), + Err(e) => { + errors.push(e.to_compile_error()); + Vec::new() + } + } + } + _ => Vec::new(), + }) + .collect(); + + input.items.retain(|item| match item { + ImplItem::Macro(item) if item.mac.path.is_ident("default") => false, + _ => true, + }); + + quote! { + #(#errors)* + + impl #generics #ty #where_clause { + #(#fwd_methods)* + } + + #input + } +} diff --git a/third_party/rust/inherent/src/lib.rs b/third_party/rust/inherent/src/lib.rs new file mode 100644 index 000000000000..7ac6390831f8 --- /dev/null +++ b/third_party/rust/inherent/src/lib.rs @@ -0,0 +1,123 @@ +//! [![github]](https://github.com/dtolnay/inherent) [![crates-io]](https://crates.io/crates/inherent) [![docs-rs]](https://docs.rs/inherent) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K +//! +//!
+//! +//! ##### An attribute macro to make trait methods callable without the trait in scope. +//! +//! # Example +//! +//! ```rust +//! mod types { +//! use inherent::inherent; +//! +//! trait Trait { +//! fn f(self); +//! } +//! +//! pub struct Struct; +//! +//! #[inherent(pub)] +//! impl Trait for Struct { +//! fn f(self) {} +//! } +//! } +//! +//! fn main() { +//! // types::Trait is not in scope, but method can be called. +//! types::Struct.f(); +//! } +//! ``` +//! +//! Without the `inherent` macro on the trait impl, this would have failed with the +//! following error: +//! +//! ```console +//! error[E0599]: no method named `f` found for type `types::Struct` in the current scope +//! --> src/main.rs:18:19 +//! | +//! 8 | pub struct Struct; +//! | ------------------ method `f` not found for this +//! ... +//! 18 | types::Struct.f(); +//! | ^ +//! | +//! = help: items from traits can only be used if the trait is implemented and in scope +//! = note: the following trait defines an item `f`, perhaps you need to implement it: +//! candidate #1: `types::Trait` +//! ``` +//! +//! The `inherent` macro expands to inherent methods on the `Self` type of the trait +//! impl that forward to the trait methods. In the case above, the generated code +//! would be: +//! +//! ```rust +//! # trait Trait { +//! # fn f(self); +//! # } +//! # +//! # pub struct Struct; +//! # +//! # impl Trait for Struct { +//! # fn f(self) {} +//! # } +//! # +//! impl Struct { +//! pub fn f(self) { +//! ::f(self) +//! } +//! } +//! ``` +//! +//! # Visibility +//! +//! Ordinary trait methods have the same visibility as the trait or the `Self` type, +//! whichever's is smaller. Neither of these visibilities is knowable to the +//! `inherent` macro, so we simply emit all inherent methods as private by default. +//! A different visibility may be specified explicitly in the `inherent` macro +//! invocation. +//! +//! ```rust +//! # use inherent::inherent; +//! # +//! # trait Trait {} +//! # +//! # struct A; +//! #[inherent] // private inherent methods are the default +//! # impl Trait for A {} +//! +//! # struct B; +//! #[inherent(pub)] // all methods pub +//! # impl Trait for B {} +//! +//! # struct C; +//! #[inherent(crate)] // all methods pub(crate) +//! # impl Trait for C {} +//! +//! # struct D; +//! #[inherent(in path::to)] // all methods pub(in path::to) +//! # impl Trait for D {} +//! ``` + +extern crate proc_macro; + +mod default_methods; +mod expand; +mod parse; +mod visibility; + +use proc_macro::TokenStream; +use syn::parse_macro_input; + +use crate::parse::TraitImpl; +use crate::visibility::Visibility; + +#[proc_macro_attribute] +pub fn inherent(args: TokenStream, input: TokenStream) -> TokenStream { + let vis = parse_macro_input!(args as Visibility); + let input = parse_macro_input!(input as TraitImpl); + expand::inherent(vis, input).into() +} diff --git a/third_party/rust/inherent/src/parse.rs b/third_party/rust/inherent/src/parse.rs new file mode 100644 index 000000000000..92a91f624b8b --- /dev/null +++ b/third_party/rust/inherent/src/parse.rs @@ -0,0 +1,69 @@ +use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::token::Brace; +use syn::{Attribute, Generics, ImplItem, ItemImpl, Path, Token, Type}; + +#[derive(Clone)] +pub struct TraitImpl { + pub attrs: Vec, + pub defaultness: Option, + pub unsafety: Option, + pub impl_token: Token![impl], + pub generics: Generics, + pub trait_: Path, + pub for_token: Token![for], + pub self_ty: Type, + pub brace_token: Brace, + pub items: Vec, +} + +impl Parse for TraitImpl { + fn parse(input: ParseStream) -> Result { + let imp: ItemImpl = input.parse()?; + + let (trait_, for_token) = match imp.trait_ { + Some((_bang_token, trait_, for_token)) => (trait_, for_token), + None => { + return Err(Error::new( + Span::call_site(), + "must be placed on a trait impl", + )) + } + }; + + Ok(TraitImpl { + attrs: imp.attrs, + defaultness: imp.defaultness, + unsafety: imp.unsafety, + impl_token: imp.impl_token, + generics: imp.generics, + trait_, + for_token, + self_ty: *imp.self_ty, + brace_token: imp.brace_token, + items: imp.items, + }) + } +} + +impl ToTokens for TraitImpl { + fn to_tokens(&self, tokens: &mut TokenStream) { + let imp = self.clone(); + + ItemImpl::to_tokens( + &ItemImpl { + attrs: imp.attrs, + defaultness: imp.defaultness, + unsafety: imp.unsafety, + impl_token: imp.impl_token, + generics: imp.generics, + trait_: Some((None, imp.trait_, imp.for_token)), + self_ty: Box::new(imp.self_ty), + brace_token: imp.brace_token, + items: imp.items, + }, + tokens, + ); + } +} diff --git a/third_party/rust/inherent/src/visibility.rs b/third_party/rust/inherent/src/visibility.rs new file mode 100644 index 000000000000..72213fda622d --- /dev/null +++ b/third_party/rust/inherent/src/visibility.rs @@ -0,0 +1,50 @@ +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::ext::IdentExt; +use syn::parse::{Parse, ParseStream, Result}; +use syn::{Ident, Path, Token, VisPublic, VisRestricted}; + +pub struct Visibility { + variant: syn::Visibility, +} + +impl Parse for Visibility { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + + let variant = if input.is_empty() { + syn::Visibility::Inherited + } else if lookahead.peek(Token![pub]) { + syn::Visibility::Public(VisPublic { + pub_token: input.parse()?, + }) + } else if lookahead.peek(Token![crate]) + || lookahead.peek(Token![self]) + || lookahead.peek(Token![super]) + { + syn::Visibility::Restricted(VisRestricted { + pub_token: Default::default(), + paren_token: Default::default(), + in_token: None, + path: Box::new(Path::from(input.call(Ident::parse_any)?)), + }) + } else if lookahead.peek(Token![in]) { + syn::Visibility::Restricted(VisRestricted { + pub_token: Default::default(), + paren_token: Default::default(), + in_token: Some(input.parse()?), + path: Box::new(input.call(Path::parse_mod_style)?), + }) + } else { + return Err(lookahead.error()); + }; + + Ok(Visibility { variant }) + } +} + +impl ToTokens for Visibility { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.variant.to_tokens(tokens); + } +} diff --git a/third_party/rust/inherent/tests/compiletest.rs b/third_party/rust/inherent/tests/compiletest.rs new file mode 100644 index 000000000000..f9aea23b5154 --- /dev/null +++ b/third_party/rust/inherent/tests/compiletest.rs @@ -0,0 +1,6 @@ +#[rustversion::attr(not(nightly), ignore)] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/third_party/rust/inherent/tests/test.rs b/third_party/rust/inherent/tests/test.rs new file mode 100644 index 000000000000..3e271d85c9c8 --- /dev/null +++ b/third_party/rust/inherent/tests/test.rs @@ -0,0 +1,27 @@ +mod types { + use inherent::inherent; + + trait Trait { + fn f(self); + // A default method + fn g(&self) {} + } + + pub struct Struct; + + #[inherent(pub)] + impl Trait for Struct { + fn f(self) {} + default! { + fn g(&self); + } + } +} + +#[test] +fn test() { + // types::Trait is not in scope. + let s = types::Struct; + s.g(); + s.f::(); +} diff --git a/third_party/rust/inherent/tests/ui/default-with-body.rs b/third_party/rust/inherent/tests/ui/default-with-body.rs new file mode 100644 index 000000000000..df2f67ee6619 --- /dev/null +++ b/third_party/rust/inherent/tests/ui/default-with-body.rs @@ -0,0 +1,18 @@ +use inherent::inherent; + +trait A { + fn a(&self) {} +} + +struct X; + +#[inherent] +impl A for X { + default! { + fn a(&self) { + println!("Default with a body >:-P"); + } + } +} + +fn main() {} diff --git a/third_party/rust/inherent/tests/ui/default-with-body.stderr b/third_party/rust/inherent/tests/ui/default-with-body.stderr new file mode 100644 index 000000000000..82dee8dd14e5 --- /dev/null +++ b/third_party/rust/inherent/tests/ui/default-with-body.stderr @@ -0,0 +1,8 @@ +error: Default method can't have a body + --> $DIR/default-with-body.rs:12:21 + | +12 | fn a(&self) { + | _____________________^ +13 | | println!("Default with a body >:-P"); +14 | | } + | |_________^ diff --git a/third_party/rust/inherent/tests/ui/nonsense-in-default.rs b/third_party/rust/inherent/tests/ui/nonsense-in-default.rs new file mode 100644 index 000000000000..907f6bdd8167 --- /dev/null +++ b/third_party/rust/inherent/tests/ui/nonsense-in-default.rs @@ -0,0 +1,16 @@ +use inherent::inherent; + +trait A { + fn a(&self) {} +} + +struct X; + +#[inherent] +impl A for X { + default! { + const NONSENSE: usize = 1; + } +} + +fn main() {} diff --git a/third_party/rust/inherent/tests/ui/nonsense-in-default.stderr b/third_party/rust/inherent/tests/ui/nonsense-in-default.stderr new file mode 100644 index 000000000000..af6ce91e3a31 --- /dev/null +++ b/third_party/rust/inherent/tests/ui/nonsense-in-default.stderr @@ -0,0 +1,5 @@ +error: expected `fn` + --> $DIR/nonsense-in-default.rs:12:15 + | +12 | const NONSENSE: usize = 1; + | ^^^^^^^^ diff --git a/third_party/rust/inherent/tests/ui/not-trait-impl.rs b/third_party/rust/inherent/tests/ui/not-trait-impl.rs new file mode 100644 index 000000000000..a9a6a1162827 --- /dev/null +++ b/third_party/rust/inherent/tests/ui/not-trait-impl.rs @@ -0,0 +1,10 @@ +use inherent::inherent; + +struct Struct; + +#[inherent] +impl Struct { + fn f() {} +} + +fn main() {} diff --git a/third_party/rust/inherent/tests/ui/not-trait-impl.stderr b/third_party/rust/inherent/tests/ui/not-trait-impl.stderr new file mode 100644 index 000000000000..afd9689a442b --- /dev/null +++ b/third_party/rust/inherent/tests/ui/not-trait-impl.stderr @@ -0,0 +1,7 @@ +error: must be placed on a trait impl + --> $DIR/not-trait-impl.rs:5:1 + | +5 | #[inherent] + | ^^^^^^^^^^^ + | + = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/third_party/rust/inherent/tests/ui/not-visible.rs b/third_party/rust/inherent/tests/ui/not-visible.rs new file mode 100644 index 000000000000..faccf90ff445 --- /dev/null +++ b/third_party/rust/inherent/tests/ui/not-visible.rs @@ -0,0 +1,18 @@ +mod types { + use inherent::inherent; + + trait Trait { + fn f(); + } + + pub struct Struct; + + #[inherent] + impl Trait for Struct { + fn f() {} + } +} + +fn main() { + types::Struct::f(); +} diff --git a/third_party/rust/inherent/tests/ui/not-visible.stderr b/third_party/rust/inherent/tests/ui/not-visible.stderr new file mode 100644 index 000000000000..1adde5455500 --- /dev/null +++ b/third_party/rust/inherent/tests/ui/not-visible.stderr @@ -0,0 +1,5 @@ +error[E0624]: associated function `f` is private + --> $DIR/not-visible.rs:17:20 + | +17 | types::Struct::f(); + | ^ private associated function diff --git a/toolkit/components/glean/Cargo.toml b/toolkit/components/glean/Cargo.toml index ca6189717a25..10cd6345591b 100644 --- a/toolkit/components/glean/Cargo.toml +++ b/toolkit/components/glean/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" license = "MPL-2.0" [dependencies] +glean = "33.1.2" glean-core = "33.1.2" log = "0.4" nserror = { path = "../../../xpcom/rust/nserror" } diff --git a/toolkit/components/glean/api/Cargo.toml b/toolkit/components/glean/api/Cargo.toml index f8659c7a1e1b..9cf347196870 100644 --- a/toolkit/components/glean/api/Cargo.toml +++ b/toolkit/components/glean/api/Cargo.toml @@ -9,6 +9,7 @@ publish = false bincode = "1.0" chrono = "0.4.10" ffi-support = "0.4" +glean = "33.1.2" glean-core = "33.1.2" log = "0.4" nsstring = { path = "../../../../xpcom/rust/nsstring", optional = true }