diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c52a543d..54628ad95 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,13 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} + # Add toolchain for no_std tests + - run: rustup toolchain install nightly + - name: Add `aarch64-unknown-none` toolchain for `no_std` tests + if: | + matrix.os == 'ubuntu-latest' && + matrix.rust == 'nightly' + run: rustup target add aarch64-unknown-none && rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu - run: cargo build --all-targets # Run tests - name: Run tests @@ -51,6 +58,13 @@ jobs: run: cargo test --test debugger_visualizer --features "url/debugger_visualizer,url_debug_tests/debugger_visualizer" -- --test-threads=1 - name: Test `no_std` support run: cargo test --no-default-features --features=alloc + - name: Build `url` crate for `aarch64-unknown-none` with `no_std` + if: | + matrix.os == 'ubuntu-latest' && + matrix.rust == 'nightly' + run: > + cd url + && cargo +nightly check -Zbuild-std=core,alloc --target aarch64-unknown-none -v --release --no-default-features --features=alloc WASM: runs-on: ubuntu-latest diff --git a/url/Cargo.toml b/url/Cargo.toml index 6fd4b994a..650a6bfab 100644 --- a/url/Cargo.toml +++ b/url/Cargo.toml @@ -10,7 +10,7 @@ documentation = "https://docs.rs/url" repository = "https://github.com/servo/rust-url" readme = "../README.md" keywords = ["url", "parser"] -categories = ["parser-implementations", "web-programming", "encoding"] +categories = ["parser-implementations", "web-programming", "encoding", "no_std"] license = "MIT OR Apache-2.0" include = ["src/**/*", "LICENSE-*", "README.md", "tests/**"] edition = "2018" @@ -25,13 +25,15 @@ bencher = "0.1" wasm-bindgen-test = "0.3" [dependencies] -form_urlencoded = { version = "1.2.1", path = "../form_urlencoded" } -idna = { version = "0.5.0", path = "../idna" } -percent-encoding = { version = "2.3.1", path = "../percent_encoding" } +form_urlencoded = { version = "1.2.1", path = "../form_urlencoded", default-features = false } +idna = { version = "0.5.0", path = "../idna", default-features = false } +percent-encoding = { version = "2.3.1", path = "../percent_encoding", default-features = false } serde = { version = "1.0", optional = true, features = ["derive"] } [features] -default = [] +default = ["std"] +std = ["alloc", "form_urlencoded/std", "idna/std", "percent-encoding/std"] +alloc = ["form_urlencoded/alloc", "idna/alloc", "percent-encoding/alloc"] # Enable to use the #[debugger_visualizer] attribute. This feature requires Rust >= 1.71. debugger_visualizer = [] # Expose internal offsets of the URL. diff --git a/url/src/host.rs b/url/src/host.rs index 7ec356326..614a32f30 100644 --- a/url/src/host.rs +++ b/url/src/host.rs @@ -6,9 +6,13 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::cmp; -use std::fmt::{self, Formatter}; -use std::net::{Ipv4Addr, Ipv6Addr}; +use alloc::borrow::ToOwned; +use alloc::string::String; +use alloc::string::ToString; +use alloc::vec::Vec; +use core::cmp; +use core::fmt::{self, Formatter}; +use std_core_compat::net::{Ipv4Addr, Ipv6Addr}; use percent_encoding::{percent_decode, utf8_percent_encode, CONTROLS}; #[cfg(feature = "serde")] diff --git a/url/src/lib.rs b/url/src/lib.rs index 33c6a7383..6573f9fa7 100644 --- a/url/src/lib.rs +++ b/url/src/lib.rs @@ -73,6 +73,14 @@ assert!(data_url.fragment() == Some("")); # run().unwrap(); ``` +## Default Features + +Versions `<= 2.5.2` of the crate have no default features. Versions `> 2.5.2` have the default feature 'std'. +If you are upgrading across this boundary and you have specified `default-features = false`, then +you will need to add the 'std' feature or the 'alloc' feature to your dependency. +The 'std' feature has the same behavior as the previous versions. The 'alloc' feature +provides no_std support. + ## Serde Enable the `serde` feature to include `Deserialize` and `Serialize` implementations for `url::Url`. @@ -139,6 +147,21 @@ url = { version = "2", features = ["debugger_visualizer"] } feature = "debugger_visualizer", debugger_visualizer(natvis_file = "../../debug_metadata/url.natvis") )] +#![cfg_attr(not(feature = "std"), no_std)] + +// Use std_core_compat for dependencies that +// are in std in the Minimum Supported Rust Version +// and in core in the latest stable release. +#[cfg(feature = "std")] +extern crate std as std_core_compat; + +#[cfg(not(feature = "std"))] +extern crate core as std_core_compat; + +extern crate alloc; + +#[cfg(not(feature = "alloc"))] +compile_error!("the `alloc` feature must currently be enabled"); pub use form_urlencoded; @@ -149,22 +172,22 @@ use crate::host::HostInternal; use crate::parser::{ to_u32, Context, Parser, SchemeType, PATH_SEGMENT, SPECIAL_PATH_SEGMENT, USERINFO, }; +use core::borrow::Borrow; +use core::cmp; +use core::fmt::{self, Write}; +use core::hash; +use core::mem; +use core::ops::{Range, RangeFrom, RangeTo}; +use core::str; use percent_encoding::{percent_decode, percent_encode, utf8_percent_encode}; -use std::borrow::Borrow; -use std::cmp; -use std::fmt::{self, Write}; -use std::hash; -#[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] -use std::io; -use std::mem; -use std::net::IpAddr; -#[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] -use std::net::{SocketAddr, ToSocketAddrs}; -use std::ops::{Range, RangeFrom, RangeTo}; -use std::path::{Path, PathBuf}; -use std::str; - -use std::convert::TryFrom; +use std_core_compat::net::IpAddr; + +use alloc::borrow::ToOwned; +use alloc::format; +use alloc::string::String; +use alloc::string::ToString; + +use core::convert::TryFrom; pub use crate::host::Host; pub use crate::origin::{OpaqueOrigin, Origin}; @@ -1276,11 +1299,16 @@ impl Url { /// }) /// } /// ``` - #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] + #[cfg(all( + feature = "std", + any(unix, windows, target_os = "redox", target_os = "wasi") + ))] pub fn socket_addrs( &self, default_port_number: impl Fn() -> Option, - ) -> io::Result> { + ) -> std::io::Result> { + use std::io; + use std::net::ToSocketAddrs; // Note: trying to avoid the Vec allocation by returning `impl AsRef<[SocketAddr]>` // causes borrowck issues because the return value borrows `default_port_number`: // @@ -2466,9 +2494,12 @@ impl Url { /// # run().unwrap(); /// # } /// ``` - #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] + #[cfg(all( + feature = "std", + any(unix, windows, target_os = "redox", target_os = "wasi") + ))] #[allow(clippy::result_unit_err)] - pub fn from_file_path>(path: P) -> Result { + pub fn from_file_path>(path: P) -> Result { let mut serialization = "file://".to_owned(); let host_start = serialization.len() as u32; let (host_end, host) = path_to_file_url_segments(path.as_ref(), &mut serialization)?; @@ -2503,9 +2534,12 @@ impl Url { /// /// Note that `std::path` does not consider trailing slashes significant /// and usually does not include them (e.g. in `Path::parent()`). - #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] + #[cfg(all( + feature = "std", + any(unix, windows, target_os = "redox", target_os = "wasi") + ))] #[allow(clippy::result_unit_err)] - pub fn from_directory_path>(path: P) -> Result { + pub fn from_directory_path>(path: P) -> Result { let mut url = Url::from_file_path(path)?; if !url.serialization.ends_with('/') { url.serialization.push('/') @@ -2620,9 +2654,12 @@ impl Url { /// (That is, if the percent-decoded path contains a NUL byte or, /// for a Windows path, is not UTF-8.) #[inline] - #[cfg(any(unix, windows, target_os = "redox", target_os = "wasi"))] + #[cfg(all( + feature = "std", + any(unix, windows, target_os = "redox", target_os = "wasi") + ))] #[allow(clippy::result_unit_err)] - pub fn to_file_path(&self) -> Result { + pub fn to_file_path(&self) -> Result { if let Some(segments) = self.path_segments() { let host = match self.host() { None | Some(Host::Domain("localhost")) => None, @@ -2824,9 +2861,9 @@ impl<'de> serde::Deserialize<'de> for Url { } } -#[cfg(any(unix, target_os = "redox", target_os = "wasi"))] +#[cfg(all(feature = "std", any(unix, target_os = "redox", target_os = "wasi")))] fn path_to_file_url_segments( - path: &Path, + path: &std::path::Path, serialization: &mut String, ) -> Result<(u32, HostInternal), ()> { #[cfg(any(unix, target_os = "redox"))] @@ -2854,9 +2891,9 @@ fn path_to_file_url_segments( Ok((host_end, HostInternal::None)) } -#[cfg(windows)] +#[cfg(all(feature = "std", windows))] fn path_to_file_url_segments( - path: &Path, + path: &std::path::Path, serialization: &mut String, ) -> Result<(u32, HostInternal), ()> { path_to_file_url_segments_windows(path, serialization) @@ -2864,8 +2901,9 @@ fn path_to_file_url_segments( // Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102 #[cfg_attr(not(windows), allow(dead_code))] +#[cfg(feature = "std")] fn path_to_file_url_segments_windows( - path: &Path, + path: &std::path::Path, serialization: &mut String, ) -> Result<(u32, HostInternal), ()> { use std::path::{Component, Prefix}; @@ -2926,16 +2964,17 @@ fn path_to_file_url_segments_windows( Ok((host_end, host_internal)) } -#[cfg(any(unix, target_os = "redox", target_os = "wasi"))] +#[cfg(all(feature = "std", any(unix, target_os = "redox", target_os = "wasi")))] fn file_url_segments_to_pathbuf( host: Option<&str>, segments: str::Split<'_, char>, -) -> Result { +) -> Result { use std::ffi::OsStr; #[cfg(any(unix, target_os = "redox"))] use std::os::unix::prelude::OsStrExt; #[cfg(target_os = "wasi")] use std::os::wasi::prelude::OsStrExt; + use std::path::PathBuf; if host.is_some() { return Err(()); @@ -2971,20 +3010,21 @@ fn file_url_segments_to_pathbuf( Ok(path) } -#[cfg(windows)] +#[cfg(all(feature = "std", windows))] fn file_url_segments_to_pathbuf( host: Option<&str>, segments: str::Split, -) -> Result { +) -> Result { file_url_segments_to_pathbuf_windows(host, segments) } // Build this unconditionally to alleviate https://github.com/servo/rust-url/issues/102 #[cfg_attr(not(windows), allow(dead_code))] +#[cfg(feature = "std")] fn file_url_segments_to_pathbuf_windows( host: Option<&str>, mut segments: str::Split<'_, char>, -) -> Result { +) -> Result { let mut string = if let Some(host) = host { r"\\".to_owned() + host } else { @@ -3024,7 +3064,7 @@ fn file_url_segments_to_pathbuf_windows( Err(..) => return Err(()), } } - let path = PathBuf::from(string); + let path = std::path::PathBuf::from(string); debug_assert!( path.is_absolute(), "to_file_path() failed to produce an absolute Path" diff --git a/url/src/origin.rs b/url/src/origin.rs index 81193f510..a039f4529 100644 --- a/url/src/origin.rs +++ b/url/src/origin.rs @@ -9,7 +9,10 @@ use crate::host::Host; use crate::parser::default_port; use crate::Url; -use std::sync::atomic::{AtomicUsize, Ordering}; +use alloc::borrow::ToOwned; +use alloc::format; +use alloc::string::String; +use core::sync::atomic::{AtomicUsize, Ordering}; pub fn url_origin(url: &Url) -> Origin { let scheme = url.scheme(); diff --git a/url/src/parser.rs b/url/src/parser.rs index 8dd054df8..b4e6c6c94 100644 --- a/url/src/parser.rs +++ b/url/src/parser.rs @@ -6,9 +6,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::error::Error; -use std::fmt::{self, Formatter, Write}; -use std::str; +use alloc::string::String; +use alloc::string::ToString; +use core::fmt::{self, Formatter, Write}; +use core::str; +use std_core_compat::error::Error; use crate::host::{Host, HostInternal}; use crate::Url; diff --git a/url/src/path_segments.rs b/url/src/path_segments.rs index d8a78d785..aa4f0b2bb 100644 --- a/url/src/path_segments.rs +++ b/url/src/path_segments.rs @@ -8,7 +8,8 @@ use crate::parser::{self, to_u32, SchemeType}; use crate::Url; -use std::str; +use alloc::string::String; +use core::str; /// Exposes methods to manipulate the path of an URL that is not cannot-be-base. /// diff --git a/url/src/quirks.rs b/url/src/quirks.rs index 391a50dbe..8626f64ca 100644 --- a/url/src/quirks.rs +++ b/url/src/quirks.rs @@ -13,6 +13,8 @@ use crate::parser::{default_port, Context, Input, Parser, SchemeType}; use crate::{Host, ParseError, Position, Url}; +use alloc::string::String; +use alloc::string::ToString; /// Internal components / offsets of a URL. /// diff --git a/url/src/slicing.rs b/url/src/slicing.rs index 13829575d..aef7ae972 100644 --- a/url/src/slicing.rs +++ b/url/src/slicing.rs @@ -7,7 +7,7 @@ // except according to those terms. use crate::Url; -use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo}; +use core::ops::{Index, Range, RangeFrom, RangeFull, RangeTo}; impl Index for Url { type Output = str;