mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 21:39:07 +00:00
Properly resolve shorthands to HTTPS URL packages
This commit is contained in:
parent
d16b247523
commit
092d21a663
5 changed files with 148 additions and 50 deletions
|
@ -13,7 +13,6 @@ roc_region = { path = "../region" }
|
||||||
roc_module = { path = "../module" }
|
roc_module = { path = "../module" }
|
||||||
roc_types = { path = "../types" }
|
roc_types = { path = "../types" }
|
||||||
roc_can = { path = "../can" }
|
roc_can = { path = "../can" }
|
||||||
roc_packaging = { path = "../../packaging" }
|
|
||||||
roc_constrain = { path = "../constrain" }
|
roc_constrain = { path = "../constrain" }
|
||||||
roc_derive_key = { path = "../derive_key" }
|
roc_derive_key = { path = "../derive_key" }
|
||||||
roc_derive = { path = "../derive" }
|
roc_derive = { path = "../derive" }
|
||||||
|
@ -28,6 +27,7 @@ roc_mono = { path = "../mono" }
|
||||||
roc_intern = { path = "../intern" }
|
roc_intern = { path = "../intern" }
|
||||||
roc_target = { path = "../roc_target" }
|
roc_target = { path = "../roc_target" }
|
||||||
roc_tracing = { path = "../../tracing" }
|
roc_tracing = { path = "../../tracing" }
|
||||||
|
roc_packaging = { path = "../../packaging" }
|
||||||
roc_reporting = { path = "../../reporting" }
|
roc_reporting = { path = "../../reporting" }
|
||||||
roc_debug_flags = { path = "../debug_flags" }
|
roc_debug_flags = { path = "../debug_flags" }
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ use roc_mono::layout::{
|
||||||
CapturesNiche, LambdaName, Layout, LayoutCache, LayoutProblem, STLayoutInterner,
|
CapturesNiche, LambdaName, Layout, LayoutCache, LayoutProblem, STLayoutInterner,
|
||||||
};
|
};
|
||||||
use roc_packaging::cache::{self, RocCacheDir};
|
use roc_packaging::cache::{self, RocCacheDir};
|
||||||
|
use roc_packaging::https::PackageMetadata;
|
||||||
use roc_parse::ast::{self, Defs, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
|
use roc_parse::ast::{self, Defs, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
|
||||||
use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent};
|
use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent};
|
||||||
use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
|
use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
|
||||||
|
@ -721,7 +722,7 @@ pub enum EntryPoint<'a> {
|
||||||
Executable {
|
Executable {
|
||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
layout: ProcLayout<'a>,
|
layout: ProcLayout<'a>,
|
||||||
platform_path: Box<Path>,
|
platform_path: PathBuf,
|
||||||
},
|
},
|
||||||
Test,
|
Test,
|
||||||
}
|
}
|
||||||
|
@ -896,6 +897,7 @@ impl MakeSpecializationsPass {
|
||||||
struct State<'a> {
|
struct State<'a> {
|
||||||
pub root_id: ModuleId,
|
pub root_id: ModuleId,
|
||||||
pub root_subs: Option<Subs>,
|
pub root_subs: Option<Subs>,
|
||||||
|
pub cache_dir: PathBuf,
|
||||||
pub platform_data: Option<PlatformData>,
|
pub platform_data: Option<PlatformData>,
|
||||||
pub exposed_types: ExposedByModule,
|
pub exposed_types: ExposedByModule,
|
||||||
pub output_path: Option<&'a str>,
|
pub output_path: Option<&'a str>,
|
||||||
|
@ -914,7 +916,7 @@ struct State<'a> {
|
||||||
|
|
||||||
/// From now on, these will be used by multiple threads; time to make an Arc<Mutex<_>>!
|
/// From now on, these will be used by multiple threads; time to make an Arc<Mutex<_>>!
|
||||||
pub arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
|
pub arc_modules: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||||
pub arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
|
pub arc_shorthands: Arc<Mutex<MutMap<&'a str, Shorthand<'a>>>>,
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub derived_module: SharedDerivedModule,
|
pub derived_module: SharedDerivedModule,
|
||||||
|
|
||||||
|
@ -969,12 +971,13 @@ impl<'a> State<'a> {
|
||||||
exec_mode: ExecutionMode,
|
exec_mode: ExecutionMode,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
|
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
|
||||||
|
let cache_dir = roc_packaging::cache::roc_cache_dir();
|
||||||
let dependencies = Dependencies::new(exec_mode.goal_phase());
|
let dependencies = Dependencies::new(exec_mode.goal_phase());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
root_id,
|
root_id,
|
||||||
root_subs: None,
|
root_subs: None,
|
||||||
|
cache_dir,
|
||||||
target_info,
|
target_info,
|
||||||
platform_data: None,
|
platform_data: None,
|
||||||
output_path: None,
|
output_path: None,
|
||||||
|
@ -1082,7 +1085,7 @@ enum BuildTask<'a> {
|
||||||
LoadModule {
|
LoadModule {
|
||||||
module_name: PQModuleName<'a>,
|
module_name: PQModuleName<'a>,
|
||||||
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||||
shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
|
shorthands: Arc<Mutex<MutMap<&'a str, Shorthand<'a>>>>,
|
||||||
ident_ids_by_module: SharedIdentIdsByModule,
|
ident_ids_by_module: SharedIdentIdsByModule,
|
||||||
},
|
},
|
||||||
Parse {
|
Parse {
|
||||||
|
@ -1601,7 +1604,15 @@ pub fn load_single_threaded<'a>(
|
||||||
|
|
||||||
// now we just manually interleave stepping the state "thread" and the worker "thread"
|
// now we just manually interleave stepping the state "thread" and the worker "thread"
|
||||||
loop {
|
loop {
|
||||||
match state_thread_step(arena, state, worker_listeners, &injector, &msg_tx, &msg_rx) {
|
match state_thread_step(
|
||||||
|
arena,
|
||||||
|
state,
|
||||||
|
&src_dir,
|
||||||
|
worker_listeners,
|
||||||
|
&injector,
|
||||||
|
&msg_tx,
|
||||||
|
&msg_rx,
|
||||||
|
) {
|
||||||
Ok(ControlFlow::Break(done)) => return Ok(done),
|
Ok(ControlFlow::Break(done)) => return Ok(done),
|
||||||
Ok(ControlFlow::Continue(new_state)) => {
|
Ok(ControlFlow::Continue(new_state)) => {
|
||||||
state = new_state;
|
state = new_state;
|
||||||
|
@ -1635,6 +1646,7 @@ pub fn load_single_threaded<'a>(
|
||||||
fn state_thread_step<'a>(
|
fn state_thread_step<'a>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
state: State<'a>,
|
state: State<'a>,
|
||||||
|
src_dir: &Path,
|
||||||
worker_listeners: &'a [Sender<WorkerMsg>],
|
worker_listeners: &'a [Sender<WorkerMsg>],
|
||||||
injector: &Injector<BuildTask<'a>>,
|
injector: &Injector<BuildTask<'a>>,
|
||||||
msg_tx: &crossbeam::channel::Sender<Msg<'a>>,
|
msg_tx: &crossbeam::channel::Sender<Msg<'a>>,
|
||||||
|
@ -1730,6 +1742,7 @@ fn state_thread_step<'a>(
|
||||||
|
|
||||||
let res_state = update(
|
let res_state = update(
|
||||||
state,
|
state,
|
||||||
|
src_dir,
|
||||||
msg,
|
msg,
|
||||||
msg_tx.clone(),
|
msg_tx.clone(),
|
||||||
injector,
|
injector,
|
||||||
|
@ -1948,8 +1961,15 @@ fn load_multi_threaded<'a>(
|
||||||
// The root module will have already queued up messages to process,
|
// The root module will have already queued up messages to process,
|
||||||
// and processing those messages will in turn queue up more messages.
|
// and processing those messages will in turn queue up more messages.
|
||||||
loop {
|
loop {
|
||||||
match state_thread_step(arena, state, worker_listeners, &injector, &msg_tx, &msg_rx)
|
match state_thread_step(
|
||||||
{
|
arena,
|
||||||
|
state,
|
||||||
|
&src_dir,
|
||||||
|
worker_listeners,
|
||||||
|
&injector,
|
||||||
|
&msg_tx,
|
||||||
|
&msg_rx,
|
||||||
|
) {
|
||||||
Ok(ControlFlow::Break(load_result)) => {
|
Ok(ControlFlow::Break(load_result)) => {
|
||||||
shut_down_worker_threads!();
|
shut_down_worker_threads!();
|
||||||
|
|
||||||
|
@ -2200,6 +2220,7 @@ fn extend_header_with_builtin(header: &mut ModuleHeader, module: ModuleId) {
|
||||||
|
|
||||||
fn update<'a>(
|
fn update<'a>(
|
||||||
mut state: State<'a>,
|
mut state: State<'a>,
|
||||||
|
src_dir: &Path,
|
||||||
msg: Msg<'a>,
|
msg: Msg<'a>,
|
||||||
msg_tx: MsgSender<'a>,
|
msg_tx: MsgSender<'a>,
|
||||||
injector: &Injector<BuildTask<'a>>,
|
injector: &Injector<BuildTask<'a>>,
|
||||||
|
@ -2232,8 +2253,56 @@ fn update<'a>(
|
||||||
{
|
{
|
||||||
let mut shorthands = (*state.arc_shorthands).lock();
|
let mut shorthands = (*state.arc_shorthands).lock();
|
||||||
|
|
||||||
for (shorthand, package_path) in header.packages.iter() {
|
for (shorthand_name, package_name) in header.packages.iter() {
|
||||||
shorthands.insert(shorthand, *package_path);
|
let package_str = package_name.as_str();
|
||||||
|
let shorthand = if package_str.starts_with("https://") {
|
||||||
|
let url = package_str;
|
||||||
|
|
||||||
|
match PackageMetadata::try_from(url) {
|
||||||
|
Ok(url_metadata) => {
|
||||||
|
// This was a valid URL
|
||||||
|
let root_module_dir = state
|
||||||
|
.cache_dir
|
||||||
|
.join(url_metadata.cache_subdir)
|
||||||
|
.join(url_metadata.content_hash);
|
||||||
|
let root_module = root_module_dir
|
||||||
|
.join(url_metadata.root_module_filename.unwrap_or("main.roc"));
|
||||||
|
|
||||||
|
Shorthand::FromHttpsUrl {
|
||||||
|
root_module_dir,
|
||||||
|
root_module,
|
||||||
|
url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(url_err) => {
|
||||||
|
todo!("Gracefully report URL error for {:?} - {:?}", url, url_err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This wasn't a URL, so it must be a filesystem path.
|
||||||
|
let relative_path = package_str;
|
||||||
|
let root_module: PathBuf = src_dir.join(relative_path);
|
||||||
|
let root_module_dir = root_module.parent().unwrap_or_else(|| {
|
||||||
|
if root_module.is_file() {
|
||||||
|
// Files must have parents!
|
||||||
|
internal_error!("Somehow I got a file path to a real file on the filesystem that has no parent!");
|
||||||
|
} else {
|
||||||
|
// TODO make this a nice report
|
||||||
|
todo!(
|
||||||
|
"platform module {:?} was not a file.",
|
||||||
|
package_str
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}).into();
|
||||||
|
|
||||||
|
Shorthand::RelativeToSrc {
|
||||||
|
root_module_dir,
|
||||||
|
root_module,
|
||||||
|
relative_path,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
shorthands.insert(shorthand_name, shorthand);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3018,26 +3087,23 @@ fn finish_specialization<'a>(
|
||||||
match exec_mode {
|
match exec_mode {
|
||||||
ExecutionMode::Test => EntryPoint::Test,
|
ExecutionMode::Test => EntryPoint::Test,
|
||||||
ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => {
|
ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => {
|
||||||
let path_to_platform = {
|
|
||||||
use PlatformPath::*;
|
use PlatformPath::*;
|
||||||
let package_name = match platform_path {
|
let platform_path = match platform_path {
|
||||||
Valid(To::ExistingPackage(shorthand)) => {
|
Valid(To::ExistingPackage(shorthand_name)) => {
|
||||||
match (*state.arc_shorthands).lock().get(shorthand) {
|
match (*state.arc_shorthands).lock().get(shorthand_name) {
|
||||||
Some(p_or_p) => *p_or_p,
|
Some(shorthand) => shorthand.root_module().to_path_buf(),
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Valid(To::NewPackage(p_or_p)) => p_or_p,
|
Valid(To::NewPackage(p_or_p)) => {
|
||||||
|
panic!("This use case of `to` (`to {}`) is deprecated. Always use a package shorthand name with `to`!", p_or_p.to_str());
|
||||||
|
}
|
||||||
other => {
|
other => {
|
||||||
let buf = to_missing_platform_report(state.root_id, other);
|
let buf = to_missing_platform_report(state.root_id, other);
|
||||||
return Err(LoadingProblem::FormattedReport(buf));
|
return Err(LoadingProblem::FormattedReport(buf));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
package_name.into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let platform_path = Path::new(path_to_platform).into();
|
|
||||||
let symbol = match platform_data {
|
let symbol = match platform_data {
|
||||||
None => {
|
None => {
|
||||||
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
debug_assert_eq!(exposed_to_host.values.len(), 1);
|
||||||
|
@ -3303,7 +3369,7 @@ fn load_module<'a>(
|
||||||
src_dir: &Path,
|
src_dir: &Path,
|
||||||
module_name: PQModuleName<'a>,
|
module_name: PQModuleName<'a>,
|
||||||
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
module_ids: Arc<Mutex<PackageModuleIds<'a>>>,
|
||||||
arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
|
arc_shorthands: Arc<Mutex<MutMap<&'a str, Shorthand<'a>>>>,
|
||||||
roc_cache_dir: RocCacheDir<'_>,
|
roc_cache_dir: RocCacheDir<'_>,
|
||||||
ident_ids_by_module: SharedIdentIdsByModule,
|
ident_ids_by_module: SharedIdentIdsByModule,
|
||||||
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
|
) -> Result<(ModuleId, Msg<'a>), LoadingProblem<'a>> {
|
||||||
|
@ -3367,10 +3433,51 @@ fn load_module<'a>(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Shorthand<'a> {
|
||||||
|
/// e.g. "/home/rtfeldman/.cache/roc/0.1.0/oUkxSOI9zFGtSoIaMB40QPdrXphr1p1780eiui2iO9Mz"
|
||||||
|
FromHttpsUrl {
|
||||||
|
/// e.g. "/home/rtfeldman/.cache/roc/0.1.0/oUkxSOI9zFGtSoIaMB40QPdrXphr1p1780eiui2iO9Mz"
|
||||||
|
root_module_dir: PathBuf,
|
||||||
|
/// e.g. "/home/rtfeldman/.cache/roc/0.1.0/oUkxSOI9zFGtSoIaMB40QPdrXphr1p1780eiui2iO9Mz/main.roc"
|
||||||
|
root_module: PathBuf,
|
||||||
|
/// e.g. "https://roc-lang.org/blah/oUkxSOI9zFGtSoIaMB40QPdrXphr1p1780eiui2iO9Mz.tar.br"
|
||||||
|
url: &'a str,
|
||||||
|
},
|
||||||
|
RelativeToSrc {
|
||||||
|
/// e.g. "/home/rtfeldman/my-roc-code/examples/cli/cli-platform/"
|
||||||
|
root_module_dir: PathBuf,
|
||||||
|
/// e.g. "/home/rtfeldman/my-roc-code/examples/cli/cli-platform/main.roc"
|
||||||
|
root_module: PathBuf,
|
||||||
|
/// what the user originally wrote, e.g. "examples/cli/cli-platform/main.roc"
|
||||||
|
relative_path: &'a str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Shorthand<'a> {
|
||||||
|
pub fn root_module(&self) -> &Path {
|
||||||
|
match self {
|
||||||
|
Shorthand::FromHttpsUrl { root_module, .. }
|
||||||
|
| Shorthand::RelativeToSrc { root_module, .. } => root_module.as_path(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root_module_dir(&self) -> &Path {
|
||||||
|
match self {
|
||||||
|
Shorthand::FromHttpsUrl {
|
||||||
|
root_module_dir, ..
|
||||||
|
}
|
||||||
|
| Shorthand::RelativeToSrc {
|
||||||
|
root_module_dir, ..
|
||||||
|
} => root_module_dir.as_path(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn module_name_to_path<'a>(
|
fn module_name_to_path<'a>(
|
||||||
src_dir: &Path,
|
src_dir: &Path,
|
||||||
module_name: &PQModuleName<'a>,
|
module_name: &PQModuleName<'a>,
|
||||||
arc_shorthands: Arc<Mutex<MutMap<&'a str, PackageName<'a>>>>,
|
arc_shorthands: Arc<Mutex<MutMap<&'a str, Shorthand<'a>>>>,
|
||||||
) -> (PathBuf, Option<&'a str>) {
|
) -> (PathBuf, Option<&'a str>) {
|
||||||
let mut filename;
|
let mut filename;
|
||||||
let opt_shorthand;
|
let opt_shorthand;
|
||||||
|
@ -3385,23 +3492,14 @@ fn module_name_to_path<'a>(
|
||||||
filename.push(part);
|
filename.push(part);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PQModuleName::Qualified(shorthand, name) => {
|
PQModuleName::Qualified(shorthand_name, name) => {
|
||||||
opt_shorthand = Some(*shorthand);
|
opt_shorthand = Some(*shorthand_name);
|
||||||
let shorthands = arc_shorthands.lock();
|
let shorthands = arc_shorthands.lock();
|
||||||
|
filename = shorthands
|
||||||
match shorthands.get(shorthand) {
|
.get(shorthand_name)
|
||||||
Some(path) => {
|
.expect("All shorthands should have been validated by now.")
|
||||||
let parent = Path::new(path.as_str()).parent().unwrap_or_else(|| {
|
.root_module_dir()
|
||||||
panic!(
|
.to_path_buf();
|
||||||
"platform module {:?} did not have a parent directory.",
|
|
||||||
path
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
filename = src_dir.join(parent)
|
|
||||||
}
|
|
||||||
None => unreachable!("there is no shorthand named {:?}", shorthand),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert dots in module name to directories
|
// Convert dots in module name to directories
|
||||||
for part in name.split(MODULE_SEPARATOR) {
|
for part in name.split(MODULE_SEPARATOR) {
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub fn install_package<'a>(
|
||||||
url: &'a str,
|
url: &'a str,
|
||||||
) -> Result<(PathBuf, Option<&'a str>), Problem> {
|
) -> Result<(PathBuf, Option<&'a str>), Problem> {
|
||||||
let PackageMetadata {
|
let PackageMetadata {
|
||||||
cache_subfolder,
|
cache_subdir,
|
||||||
content_hash,
|
content_hash,
|
||||||
root_module_filename,
|
root_module_filename,
|
||||||
} = PackageMetadata::try_from(url).map_err(Problem::InvalidUrl)?;
|
} = PackageMetadata::try_from(url).map_err(Problem::InvalidUrl)?;
|
||||||
|
@ -41,7 +41,7 @@ pub fn install_package<'a>(
|
||||||
match roc_cache_dir {
|
match roc_cache_dir {
|
||||||
RocCacheDir::Persistent(cache_dir) => {
|
RocCacheDir::Persistent(cache_dir) => {
|
||||||
// e.g. ~/.cache/roc/example.com/roc-packages/
|
// e.g. ~/.cache/roc/example.com/roc-packages/
|
||||||
let parent_dir = cache_dir.join(cache_subfolder);
|
let parent_dir = cache_dir.join(cache_subdir);
|
||||||
// e.g. ~/.cache/roc/example.com/roc-packages/jDRlAFAA3738vu3-vMpLUoyxtA86Z7CaZneoOKrihbE
|
// e.g. ~/.cache/roc/example.com/roc-packages/jDRlAFAA3738vu3-vMpLUoyxtA86Z7CaZneoOKrihbE
|
||||||
let dest_dir = parent_dir.join(content_hash);
|
let dest_dir = parent_dir.join(content_hash);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub struct PackageMetadata<'a> {
|
||||||
/// The BLAKE3 hash of the tarball's contents. Also the .tar filename on disk.
|
/// The BLAKE3 hash of the tarball's contents. Also the .tar filename on disk.
|
||||||
pub content_hash: &'a str,
|
pub content_hash: &'a str,
|
||||||
/// On disk, this will be the subfolder inside the cache dir where the package lives
|
/// On disk, this will be the subfolder inside the cache dir where the package lives
|
||||||
pub cache_subfolder: &'a str,
|
pub cache_subdir: &'a str,
|
||||||
/// Other code will default this to main.roc, but this module isn't concerned with that default.
|
/// Other code will default this to main.roc, but this module isn't concerned with that default.
|
||||||
pub root_module_filename: Option<&'a str>,
|
pub root_module_filename: Option<&'a str>,
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ impl<'a> PackageMetadata<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(PackageMetadata {
|
Ok(PackageMetadata {
|
||||||
cache_subfolder: path,
|
cache_subdir: path,
|
||||||
content_hash: tarball_name,
|
content_hash: tarball_name,
|
||||||
root_module_filename: fragment,
|
root_module_filename: fragment,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
mod https;
|
pub mod https;
|
||||||
pub mod tarball;
|
pub mod tarball;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue