1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cspell:ignore cppdocs pipenv pipfile
5
6use anyhow::{Context, Result};
7use std::ffi::OsString;
8use std::path::{Path, PathBuf};
9
10#[path = "../../api/cpp/cbindgen.rs"]
11mod cbindgen;
12
13fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
14 if dst.as_ref().exists() {
15 std::fs::remove_file(path:dst.as_ref()).context("Error removing old symlink")?;
16 }
17 #[cfg(target_os = "windows")]
18 return std::os::windows::fs::symlink_file(&src, &dst).context("Error creating symlink");
19 #[cfg(not(target_os = "windows"))]
20 return std::os::unix::fs::symlink(&src, &dst).context(format!(
21 "Error creating symlink from {} to {}",
22 src.as_ref().display(),
23 dst.as_ref().display()
24 ));
25}
26
27fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
28 if dst.as_ref().exists() {
29 std::fs::remove_dir_all(path:dst.as_ref()).context("Error removing old symlink")?;
30 }
31 #[cfg(target_os = "windows")]
32 return std::os::windows::fs::symlink_dir(&src, &dst).context("Error creating symlink");
33 #[cfg(not(target_os = "windows"))]
34 return std::os::unix::fs::symlink(&src, &dst).context(format!(
35 "Error creating symlink from {} to {}",
36 src.as_ref().display(),
37 dst.as_ref().display()
38 ));
39}
40
41fn symlink_files_in_dir<S: AsRef<Path>, T: AsRef<Path>, TS: AsRef<Path>>(
42 src: S,
43 target: T,
44 target_to_source: TS,
45) -> Result<()> {
46 for entry: Result in std::fs::read_dir(path:src.as_ref()).context("Error reading docs source directory")? {
47 let entry: DirEntry = entry.context("Error reading directory entry")?;
48 let path: PathBuf = entry.path();
49 let file_name: &OsStr = path.file_name().unwrap();
50 let symlink_source: PathBuf = target_to_source.as_ref().to_path_buf().join(&file_name);
51 let symlink_target: PathBuf = target.as_ref().to_path_buf().join(path.file_name().unwrap());
52 let filetype: FileType = entry.file_type().context("Cannot determine file type")?;
53 if filetype.is_file() {
54 symlink_file(src:symlink_source, dst:symlink_target).context("Could not symlink file")?;
55 } else if filetype.is_dir() {
56 symlink_dir(src:symlink_source, dst:symlink_target).context("Could not symlink directory")?;
57 }
58 }
59 Ok(())
60}
61
62pub fn generate(show_warnings: bool, experimental: bool) -> Result<(), Box<dyn std::error::Error>> {
63 let root = super::root_dir();
64
65 let docs_source_dir = root.join("api/cpp");
66 let docs_build_dir = root.join("target/cppdocs");
67 let html_static_dir = docs_build_dir.join("_static");
68
69 std::fs::create_dir_all(docs_build_dir.as_path()).context("Error creating docs build dir")?;
70 std::fs::create_dir_all(html_static_dir.as_path())
71 .context("Error creating _static path for docs build")?;
72
73 symlink_files_in_dir(
74 docs_source_dir.join("docs"),
75 &docs_build_dir,
76 ["..", "..", "api", "cpp", "docs"].iter().collect::<PathBuf>(),
77 )
78 .context("Error creating symlinks from docs source to docs build dir")?;
79
80 symlink_file(
81 ["..", "..", "api", "cpp", "README.md"].iter().collect::<PathBuf>(),
82 docs_build_dir.join("README.md"),
83 )?;
84
85 let generated_headers_dir = docs_build_dir.join("generated_include");
86 let enabled_features = cbindgen::EnabledFeatures {
87 interpreter: true,
88 testing: true,
89 backend_qt: true,
90 backend_winit: true,
91 backend_winit_x11: false,
92 backend_winit_wayland: false,
93 backend_linuxkms: true,
94 backend_linuxkms_noseat: false,
95 renderer_femtovg: true,
96 renderer_skia: true,
97 renderer_skia_opengl: false,
98 renderer_skia_vulkan: false,
99 renderer_software: true,
100 gettext: true,
101 accessibility: true,
102 system_testing: true,
103 freestanding: true,
104 experimental,
105 };
106 cbindgen::gen_all(&root, &generated_headers_dir, enabled_features)?;
107
108 let pip_env = vec![(OsString::from("PIPENV_PIPFILE"), docs_source_dir.join("docs/Pipfile"))];
109
110 println!("Generating third-party license list with cargo-about");
111
112 let cargo_about_output = super::run_command(
113 "cargo",
114 &[
115 "about",
116 "generate",
117 "--manifest-path",
118 "api/cpp/Cargo.toml",
119 "api/cpp/docs/thirdparty.hbs",
120 "-o",
121 docs_build_dir.join("thirdparty.md").to_str().unwrap(),
122 ],
123 pip_env.clone(),
124 )?;
125
126 println!(
127 "{}\n{}",
128 String::from_utf8_lossy(&cargo_about_output.stdout),
129 String::from_utf8_lossy(&cargo_about_output.stderr)
130 );
131
132 println!("Running pipenv install");
133
134 super::run_command("pipenv", &["install"], pip_env.clone())
135 .context("Error running pipenv install")?;
136
137 println!("Running sphinx-build");
138
139 let output = super::run_command(
140 "pipenv",
141 &[
142 "run",
143 "sphinx-build",
144 docs_build_dir.to_str().unwrap(),
145 docs_build_dir.join("html").to_str().unwrap(),
146 ],
147 pip_env,
148 )
149 .context("Error running pipenv install")?;
150
151 println!("{}", String::from_utf8_lossy(&output.stdout));
152
153 if show_warnings {
154 println!("{}", String::from_utf8_lossy(&output.stderr));
155 }
156
157 Ok(())
158}
159