Fork of daniellemaywood.uk/gleam — Wasm codegen work
8.1 kB
248 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2020 The Gleam contributors
3
4use std::{collections::HashMap, time::SystemTime};
5
6use camino::{Utf8Path, Utf8PathBuf};
7use ecow::EcoString;
8
9use crate::{cli, fs::ProjectIO, http::HttpClient};
10use gleam_core::{
11 Result,
12 analyse::TargetSupport,
13 build::{Codegen, Compile, Mode, Options, Package, Target},
14 config::{DocsPage, PackageConfig},
15 docs::{Dependency, DependencyKind, DocContext},
16 error::Error,
17 hex,
18 io::HttpClient as _,
19 manifest::ManifestPackageSource,
20 paths::ProjectPaths,
21 type_,
22};
23
24pub fn remove(package: String, version: String) -> Result<()> {
25 let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime");
26 let http = HttpClient::new();
27 let hex_config = hexpm::Config::new();
28 let credentials = crate::hex::HexAuthentication::new(&runtime, &http, hex_config.clone())
29 .get_or_create_api_credentials()?;
30
31 // Remove docs from API
32 let request = hexpm::api_remove_docs_request(
33 &package,
34 &version,
35 &crate::hex::write_credentials(&credentials)?,
36 &hex_config,
37 )
38 .map_err(Error::hex)?;
39 let response = runtime.block_on(http.send(request))?;
40 hexpm::api_remove_docs_response(response).map_err(Error::hex)?;
41
42 // Done!
43 println!("The docs for {package} {version} have been removed from HexDocs");
44 Ok(())
45}
46
47#[derive(Debug)]
48pub struct BuildOptions {
49 /// Whether to open the docs after building.
50 pub open: bool,
51 pub target: Option<Target>,
52}
53
54pub fn build(paths: &ProjectPaths, options: BuildOptions) -> Result<()> {
55 let config = crate::config::root_config(paths)?;
56
57 // Reset the build directory for the root package so that's recompiled and
58 // the docs can be up to date for all modules.
59 // Note how this doesn't delete the entire build directory, this way we can
60 // avoid recompiling all the dependencies every time we're building the
61 // documentation for our package.
62 crate::fs::delete_directory(&paths.build_directory_for_package(
63 Mode::Prod,
64 options.target.unwrap_or(Target::Erlang),
65 &config.name,
66 ))?;
67
68 let out = paths.build_documentation_directory(&config.name);
69
70 let manifest = crate::build::download_dependencies(paths, cli::Reporter::new())?;
71 let dependencies = manifest
72 .packages
73 .iter()
74 .map(|package| {
75 (
76 package.name.clone(),
77 Dependency {
78 version: package.version.clone(),
79 kind: match &package.source {
80 ManifestPackageSource::Hex { .. } => DependencyKind::Hex,
81 ManifestPackageSource::Git { .. } => DependencyKind::Git,
82 ManifestPackageSource::Local { .. } => DependencyKind::Path,
83 },
84 },
85 )
86 })
87 .collect();
88
89 let mut built = crate::build::main(
90 paths,
91 Options {
92 mode: Mode::Prod,
93 target: options.target,
94 // Code generation is not needed when building docs, this can speed
95 // up the docs building process quite drastically, especially on the
96 // Erlang target where we can avoid calling erlc.
97 codegen: Codegen::None,
98 compile: Compile::All,
99 warnings_as_errors: false,
100 root_target_support: TargetSupport::Enforced,
101 no_print_progress: false,
102 },
103 manifest,
104 )?;
105 let outputs = build_documentation(
106 paths,
107 &config,
108 dependencies,
109 &mut built.root_package,
110 DocContext::Build,
111 &built.module_interfaces,
112 )?;
113
114 // Write
115 crate::fs::delete_directory(&out)?;
116 crate::fs::write_outputs_under(&outputs, &out)?;
117
118 let index_html = out.join("index.html");
119
120 println!(
121 "\nThe documentation for {package} has been rendered to \n{index_html}",
122 package = config.name,
123 index_html = index_html
124 );
125
126 if options.open {
127 open_docs(&index_html)?;
128 }
129
130 // We're done!
131 Ok(())
132}
133
134/// Opens the indicated path in the default program configured by the system.
135///
136/// For the docs this will generally be a browser (unless some other program is
137/// configured as the default for `.html` files).
138fn open_docs(path: &Utf8Path) -> Result<()> {
139 opener::open(path).map_err(|error| Error::FailedToOpenDocs {
140 path: path.to_path_buf(),
141 error: error.to_string(),
142 })?;
143
144 Ok(())
145}
146
147pub(crate) fn build_documentation(
148 paths: &ProjectPaths,
149 config: &PackageConfig,
150 dependencies: HashMap<EcoString, Dependency>,
151 compiled: &mut Package,
152 is_hex_publish: DocContext,
153 cached_modules: &im::HashMap<EcoString, type_::ModuleInterface>,
154) -> Result<Vec<gleam_core::io::OutputFile>, Error> {
155 compiled.attach_doc_and_module_comments();
156 cli::print_generating_documentation();
157 let mut pages = vec![DocsPage {
158 title: "README".into(),
159 path: "index.html".into(),
160 source: paths.readme(), // TODO: support non markdown READMEs. Or a default if there is none.
161 }];
162 pages.extend(config.documentation.pages.iter().cloned());
163 let mut outputs = gleam_core::docs::generate_html(
164 paths,
165 gleam_core::docs::DocumentationConfig {
166 package_config: config,
167 dependencies,
168 analysed: compiled.modules.as_slice(),
169 docs_pages: &pages,
170 rendering_timestamp: SystemTime::now(),
171 context: is_hex_publish,
172 },
173 ProjectIO::new(),
174 );
175
176 outputs.push(gleam_core::docs::generate_json_package_interface(
177 Utf8PathBuf::from("package-interface.json"),
178 compiled,
179 cached_modules,
180 ));
181 Ok(outputs)
182}
183
184pub fn publish(paths: &ProjectPaths) -> Result<()> {
185 let config = crate::config::root_config(paths)?;
186 let http = HttpClient::new();
187 let runtime = tokio::runtime::Runtime::new().expect("Unable to start Tokio async runtime");
188 let hex_config = hexpm::Config::new();
189 let credentials = crate::hex::HexAuthentication::new(&runtime, &http, hex_config.clone())
190 .get_or_create_api_credentials()?;
191
192 // Reset the build directory so we know the state of the project
193 crate::fs::delete_directory(&paths.build_directory_for_target(Mode::Prod, config.target))?;
194
195 let manifest = crate::build::download_dependencies(paths, cli::Reporter::new())?;
196 let dependencies = manifest
197 .packages
198 .iter()
199 .map(|package| {
200 (
201 package.name.clone(),
202 Dependency {
203 version: package.version.clone(),
204 kind: match &package.source {
205 ManifestPackageSource::Hex { .. } => DependencyKind::Hex,
206 ManifestPackageSource::Git { .. } => DependencyKind::Git,
207 ManifestPackageSource::Local { .. } => DependencyKind::Path,
208 },
209 },
210 )
211 })
212 .collect();
213
214 let mut built = crate::build::main(
215 paths,
216 Options {
217 root_target_support: TargetSupport::Enforced,
218 warnings_as_errors: false,
219 codegen: Codegen::All,
220 compile: Compile::All,
221 mode: Mode::Prod,
222 target: None,
223 no_print_progress: false,
224 },
225 manifest,
226 )?;
227 let outputs = build_documentation(
228 paths,
229 &config,
230 dependencies,
231 &mut built.root_package,
232 DocContext::HexPublish,
233 &built.module_interfaces,
234 )?;
235 let archive = crate::fs::create_tar_archive(outputs)?;
236
237 cli::print_publishing_documentation();
238 runtime.block_on(hex::publish_documentation(
239 &config.name,
240 &config.version,
241 archive,
242 &crate::hex::write_credentials(&credentials)?,
243 &hex_config,
244 &http,
245 ))?;
246 cli::print_published("documentation");
247 Ok(())
248}