Make C++ documentation generation an xtask

Tear it out of the CMakeLists.txt and instead run it via

    cargo xtask cppdocs

This allows the build_and_test step in the CI to only run cmake for the
library/header related bits and the docs_and_demos step to only generate
docs and not require a full host build of the library (as cargo xtask
cmake would otherwise do).
This commit is contained in:
Simon Hausmann 2020-09-01 16:40:48 +02:00 committed by Simon Hausmann
parent 704644c752
commit 65ff715fbc
6 changed files with 152 additions and 46 deletions

View file

@ -85,10 +85,6 @@ jobs:
with:
command: test
args: --verbose
- name: Install doxygen
if: matrix.os == 'ubuntu-20.04'
run: sudo apt-get install doxygen
- uses: dschep/install-pipenv-action@v1
- name: Prepare cmake module
run: cargo xtask cmake --prefix $HOME/sixtyfps_install --install
- name: C++ Test
@ -147,8 +143,8 @@ jobs:
- name: Install doxygen
run: sudo apt-get install doxygen
- uses: dschep/install-pipenv-action@v1
- name: Prepare cmake module (builds docs)
run: cargo xtask cmake
- name: Build Cpp docs
run: cargo xtask cppdocs
- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Setup Node.js
@ -183,7 +179,7 @@ jobs:
examples/printerdemo/wasm/dist/
api/sixtyfps-wasm-interpreter/pkg/
target/doc
target/debug/docs/html
target/cppdocs/html
- name: Clean cache # Otherwise the cache is much too big
run: |
du -hs target
@ -223,7 +219,7 @@ jobs:
rm -rf docs
mkdir -p docs
mkdir -p docs/cpp
cp -a ../target/debug/docs/html/* docs/cpp/
cp -a ../target/cppdocs/html/* docs/cpp/
mkdir -p docs/rust
cp -a ../target/doc/* docs/rust/
git add docs

View file

@ -81,21 +81,3 @@ install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/lib/cmake/SixtyFPS/SixtyFPSConfigVersion.cmake"
DESTINATION lib/cmake/SixtyFPS
)
find_package(Doxygen)
find_program(PipEnv pipenv)
if (DOXYGEN_FOUND)
if (PipEnv)
configure_file(../docs/conf.py.in docs/conf.py)
file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/../docs/index.rst ${CMAKE_CURRENT_BINARY_DIR}/docs/index.rst SYMBOLIC)
file(CREATE_LINK ${CMAKE_CURRENT_SOURCE_DIR}/../README.md ${CMAKE_CURRENT_BINARY_DIR}/docs/README.md SYMBOLIC)
get_filename_component(markdown_docs_source ../../../docs ABSOLUTE)
file(CREATE_LINK ${markdown_docs_source} ${CMAKE_CURRENT_BINARY_DIR}/docs/markdown SYMBOLIC)
execute_process(COMMAND ${CMAKE_COMMAND} -E env PIPENV_PIPFILE=${CMAKE_CURRENT_SOURCE_DIR}/../docs/Pipfile ${PipEnv} install)
add_custom_target(docs ALL COMMAND ${CMAKE_COMMAND} -E env PIPENV_PIPFILE=${CMAKE_CURRENT_SOURCE_DIR}/../docs/Pipfile ${PipEnv} run sphinx-build ./docs ./docs/html)
else()
message("Pipenv not found, not building documentation")
endif()
else()
message("Doxygen not found, not building documentation")
endif()

View file

@ -44,7 +44,7 @@ exhale_args = {
"doxygenStripFromPath": "..",
"createTreeView": True,
"exhaleExecutesDoxygen": True,
"exhaleDoxygenStdin": '''INPUT = @CMAKE_CURRENT_SOURCE_DIR@/../include
"exhaleDoxygenStdin": '''INPUT = ../../api/sixtyfps-cpp/include
EXCLUDE_SYMBOLS = sixtyfps::cbindgen_private* sixtyfps::private_api*'''
}

107
xtask/src/cppdocs.rs Normal file
View file

@ -0,0 +1,107 @@
/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
SPDX-License-Identifier: GPL-3.0-only
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
use anyhow::{Context, Result};
use std::ffi::OsString;
use std::path::{Path, PathBuf};
fn symlink_file<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
if dst.as_ref().exists() {
std::fs::remove_file(dst.as_ref()).context("Error removing old symlink")?;
}
#[cfg(target_os = "windows")]
return std::os::windows::fs::symlink_file(&src, &dst).context("Error creating symlink");
#[cfg(not(target_os = "windows"))]
return std::os::unix::fs::symlink(&src, &dst).context(format!(
"Error creating symlink from {} to {}",
src.as_ref().display(),
dst.as_ref().display()
));
}
fn symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) -> Result<()> {
if dst.as_ref().exists() {
std::fs::remove_file(dst.as_ref()).context("Error removing old symlink")?;
}
#[cfg(target_os = "windows")]
return std::os::windows::fs::symlink_dir(&src, &dst).context("Error creating symlink");
#[cfg(not(target_os = "windows"))]
return std::os::unix::fs::symlink(&src, &dst).context(format!(
"Error creating symlink from {} to {}",
src.as_ref().display(),
dst.as_ref().display()
));
}
fn symlink_files_in_dir<S: AsRef<Path>, T: AsRef<Path>, TS: AsRef<Path>>(
src: S,
target: T,
target_to_source: TS,
) -> Result<()> {
for entry in std::fs::read_dir(src.as_ref()).context("Error reading docs source directory")? {
let entry = entry.context("Error reading directory entry")?;
let path = entry.path();
if path.is_file() {
let file_name = path.file_name().unwrap();
let symlink_source = target_to_source.as_ref().to_path_buf().join(&file_name);
let symlink_target = target.as_ref().to_path_buf().join(path.file_name().unwrap());
symlink_file(symlink_source, symlink_target)?;
}
}
Ok(())
}
pub fn generate() -> Result<(), Box<dyn std::error::Error>> {
let root = super::root_dir()?;
let docs_source_dir = root.join("api/sixtyfps-cpp");
let docs_build_dir = root.join("target/cppdocs");
std::fs::create_dir_all(docs_build_dir.as_path()).context("Error creating docs build dir")?;
symlink_files_in_dir(
docs_source_dir.join("docs"),
&docs_build_dir,
["..", "..", "api", "sixtyfps-cpp", "docs"].iter().collect::<PathBuf>(),
)
.context("Error symlinking files from docs source to docs build dir")?;
symlink_dir(["..", "..", "docs"].iter().collect::<PathBuf>(), docs_build_dir.join("markdown"))?;
symlink_file(
["..", "..", "api", "sixtyfps-cpp", "README.md"].iter().collect::<PathBuf>(),
docs_build_dir.join("README.md"),
)?;
let pip_env =
vec![(OsString::from("PIPENV_PIPFILE"), docs_source_dir.join("docs/Pipfile").to_owned())];
println!("Running pipenv install");
super::run_command("pipenv", &["install"], pip_env.clone())
.context("Error running pipenv install")?;
println!("Running sphinx-build");
let output = super::run_command(
"pipenv",
&[
"run",
"sphinx-build",
docs_build_dir.to_str().unwrap(),
docs_build_dir.join("html").to_str().unwrap(),
],
pip_env.clone(),
)
.context("Error running pipenv install")?;
println!("{}", String::from_utf8_lossy(&output));
Ok(())
}

View file

@ -11,7 +11,7 @@ use anyhow::Context;
use anyhow::Result;
use lazy_static::lazy_static;
use std::str::FromStr;
use std::{path::Path, path::PathBuf, process::Command};
use std::{path::Path, path::PathBuf};
use structopt::StructOpt;
#[derive(Copy, Clone, Debug)]
@ -208,7 +208,7 @@ lazy_static! {
(".*\\.gitignore$", LicenseLocation::NoLicense),
("\\.clang-format$", LicenseLocation::NoLicense),
("^api/sixtyfps-cpp/docs/Pipfile$", LicenseLocation::NoLicense),
("^api/sixtyfps-cpp/docs/conf.py.in$", LicenseLocation::NoLicense),
("^api/sixtyfps-cpp/docs/conf.py$", LicenseLocation::NoLicense),
("\\.cargo/config$", LicenseLocation::Tag(LicenseTagStyle::shell_comment_style())),
(
"\\.github/workflows/rust.yaml$",
@ -264,25 +264,13 @@ const EXPECTED_HEADER: LicenseHeader<'static> = LicenseHeader(&[
const EXPECTED_HOMEPAGE: &str = "https://sixtyfps.io";
const EXPECTED_REPOSITORY: &str = "https://github.com/sixtyfpsui/sixtyfps";
fn run_command(program: &str, args: &[&str]) -> Result<Vec<u8>> {
let cmdline = || format!("{} {}", program, args.join(" "));
let output = Command::new(program)
.args(args)
.current_dir(super::root_dir()?)
.output()
.with_context(|| format!("Error launching {}", cmdline()))?;
let code =
output.status.code().with_context(|| format!("Command received signal: {}", cmdline()))?;
if code != 0 {
Err(anyhow::anyhow!("Command {} exited with non-zero status: {}", cmdline(), code))
} else {
Ok(output.stdout)
}
}
fn collect_files() -> Result<Vec<PathBuf>> {
let root = super::root_dir()?;
let ls_files_output = run_command("git", &["ls-files", "-z"])?;
let ls_files_output = super::run_command(
"git",
&["ls-files", "-z"],
std::iter::empty::<(std::ffi::OsString, std::ffi::OsString)>(),
)?;
let mut files = Vec::new();
for path in ls_files_output.split(|ch| *ch == 0) {
if path.is_empty() {

View file

@ -7,11 +7,13 @@
This file is also available under commercial licensing terms.
Please contact info@sixtyfps.io for more information.
LICENSE END */
use anyhow::Context;
use std::error::Error;
use std::path::PathBuf;
use structopt::StructOpt;
mod cmake;
mod cppdocs;
mod license_headers_check;
#[derive(Debug, StructOpt)]
@ -20,6 +22,8 @@ pub enum TaskCommand {
CMake(cmake::CMakeCommand),
#[structopt(name = "check_license_headers")]
CheckLicenseHeaders(license_headers_check::LicenseHeaderCheck),
#[structopt(name = "cppdocs")]
CppDocs,
}
#[derive(Debug, StructOpt)]
@ -35,10 +39,39 @@ pub fn root_dir() -> anyhow::Result<PathBuf> {
Ok(root)
}
fn run_command<I, K, V>(program: &str, args: &[&str], env: I) -> anyhow::Result<Vec<u8>>
where
I: IntoIterator<Item = (K, V)>,
K: AsRef<std::ffi::OsStr>,
V: AsRef<std::ffi::OsStr>,
{
let cmdline = || format!("{} {}", program, args.join(" "));
let output = std::process::Command::new(program)
.args(args)
.current_dir(root_dir()?)
.envs(env)
.output()
.with_context(|| format!("Error launching {}", cmdline()))?;
let code =
output.status.code().with_context(|| format!("Command received signal: {}", cmdline()))?;
if code != 0 {
Err(anyhow::anyhow!(
"Command {} exited with non-zero status: {}\nstdout: {}\nstderr: {}",
cmdline(),
code,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
))
} else {
Ok(output.stdout)
}
}
fn main() -> Result<(), Box<dyn Error>> {
match ApplicationArguments::from_args().command {
TaskCommand::CMake(cmd) => cmd.build_cmake()?,
TaskCommand::CheckLicenseHeaders(cmd) => cmd.check_license_headers()?,
TaskCommand::CppDocs => cppdocs::generate()?,
};
Ok(())