Project Reports & Websites

Steven J Zeil

Last modified: Nov 30, 2020
Contents:

Abstract

In this lesson we will continue looking at project documentation:

We will look at how to use our automated build tools to keep project reporting up-to-date.

1 Project Reports

1.1 Test Reports

We’ve already looked at JUnit, which can be used to generate test reports like this one or this one.

This is generated in Gradle via the java plugin. It produces an XML summary and a formatted reports in build\reports.

1.2 Static Code Analyzers

Many tools that we will cover later for analyzing code can produce useful (or at least, impressive) documentation as a side effect.

1.3 Configuration Reports

Configuration managers generate reports about the dependencies among the software components.

For example, this report comes from Gradle by adding

plugins {
     ⋮
   id 'project-report'
     ⋮
}

to the build.xml file

2 Project Websites

2.1 Case study: Constructing a project website with Gradle

Let’s look at the process of building a simple website that provides

  1. A brief project description
  2. Links to (and copies of)

We can consider adding other content in later lessons.

2.1.1 Build plan

After a build, we can construct a website in build/reports by

  1. Copying build/doc/ into build/website

    • This gets a copy of the Javadocs

    • Note that the JUnit and project configuration reports will already be in subdirectories of build/reports.

  2. Copying HTML, CSS, and Javascript files from src/main/html into reports.

    • Hand-crafted pages with the project description and links to the three reports.

2.1.2 The HTML File

home_html.listing
<!DOCTYPE html>
<html>
	<head>
	    <title>Project Reports</title>
	    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
	    <link rel="stylesheet" type="text/css" href="projectReports.css"/>
	    <script src="projectReports.js"></script>
	</head>
	<body>
	    <h1>CodeCompCommon</h1>

        <p>This project provides a common set of interfaces for all CS350
           CodeComp project variants.</p>

        <p>It also provides a SharedPhrases class used to store token lists
           obtained via lexical analysis of the source code.</p>

        <h2>Information</h2>
		
        <p>Current version: 1.3</p>
        
	    <h2>Detailed Reports</h2>
		
	    <div class="reportLinks">
	   		<ul>
	   		    <li>General:
	   		        <ul>
	   			       <li><a href="../docs/javadoc/">JavaDoc</a></li>
	   			       <li><a href="../project/dependencies/root.html">Project Dependencies</a></li>
	   			    </ul></li>
	   			<li>Tests:
	   			    <ul>
	   			        <li><a href="../tests/test/">JUnit tests</a></li>
                    </ul>					
				</li>	    			
	   		</ul>
	   	</div>
	    
	</body>
</html>

2.1.3 Gradle setup

After a build, we can construct a website in build/reports by

  1. Copying build/doc/ into build/website
  2. Copying HTML, CSS, and Javascript files from src/main/html into reports.
    test.ignoreFailures=true                ➀
    check.dependsOn htmlDependencyReport    ➁
    
    task copyWebPages (type: Copy) {        ➄
        from 'src/main/html'
        into 'build/reports'
    }
    
    task copyDocs (type: Copy) {            ➃
        from 'build/docs'
        into 'build/reports/docs'
        dependsOn 'javadoc'
    }
    
    task buildSite (dependsOn: ['javadoc', 'check',             ➂
                          'copyWebPages', 'copyDocs']) {
        description 'Generate reports website for this project'
    }
    

2.2 Case study: Constructing a project website with Gradle and Jbake

Example

For a more elaborate, multi-page site, we can use a website generator to make it easy to enforce a common look-and-feel across pages.

Jbake injects content uses a series of templates

2.2.1 Gradle & Jbake

Gradle has a jbake plugin that adds a bake task to generate a website.

2.2.2 The website setup

src
├── jbake
│   ├── assets
│   │   ├── css
│   │   │   └── base.css
│   │   └── js
│   │       └── jquery-1.11.1.min.js
│   ├── content
│   │   ├── dependencies.html
│   │   ├── home0.md
│   │   ├── javadoc.html
│   │   └── junit.html
│   ├── jbake.properties
│   └── templates
│       ├── footer.ftl
│       ├── header.ftl
│       ├── index.ftl
│       ├── menu.ftl
│       ├── page.ftl
│       └── sitemap.ftl

The content directory` holds the actual information we want to disseminate.

The templates directory determines the “look and feel” of the website.

Templates:

page.ftl.listing
<#include "header.ftl">

	<div class="page-header">
		<h1><#escape x as x?xml>${content.title}</#escape></h1>
	</div>
	<p><em>${content.date?string("dd MMMM yyyy")}</em></p>
	<div class="center">
	    <#include "menu.ftl">
		<div class="rightpart">
			<p>${content.body}</p>
		</div>
	</div>
	<hr />

<#include "footer.ftl">
header.ftl.listing
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <title><#if (content.title)??><#escape x as x?xml>${content.title}</#escape><#else>codecentric</#if></title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="">
    <meta name="keywords" content="">
    <meta name="generator" content="JBake">

    <link href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/base.css" rel="stylesheet">

    <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
    <!--[if lt IE 9]>
      <script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/html5shiv.min.js"></script>
    <![endif]-->

    <link rel="shortcut icon" href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>favicon.ico">
  </head>
  <body>
    <div id="mainBody">
   
menu.ftl.listing
<div class="leftPart">
  <div class="menuBlock">
    <span class="menuBlockHeader"><a href="<#if
										   (content.rootpath)??>${content.rootpath}<#else></#if>home.html">Home</a></span>
  </div>
  <div class="menuBlock">
    <span class="menuBlockHeader">Project Info</span>
	<ul>
      <li><a href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>javadoc.html">API (Javadoc)</a></li>
      <li><a href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>dependencies.html">Dependencies</a></li>
	</ul>
  </div>
  <div class="menuBlock">
    <span class="menuBlockHeader">Testing</span>
	<ul>
      <li><a href="<#if
      (content.rootpath)??>${content.rootpath}<#else></#if>junit.html">Unit
		  Tests</a></li>
	  <!--
      <li><a href="<#if
		(content.rootpath)??>${content.rootpath}<#else></#if>jacoco.html">Coverage</a></li>
-->
	</ul>
  </div>
  <!--
  <div class="menuBlock">
 	<span class="menuBlockHeader">Analysis Reports</span>
	<ul>
      <li><a href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>checkstyle.html">Checkstyle</a></li>
      <li><a href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>dependencies.html">Dependencies</a></li>
	</ul>
  </div>
-->
</div>
footer.ftl.listing
    </div>
    
    <div id="footer">
      <div class="container">
        <p class="muted credit">&copy; 2019 Old Dominion University</p>
      </div>
    </div>
    
    <script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/jquery-1.11.1.min.js"></script>
    
  </body>
</html>

Content:

junit.html.listing
title=CodeCompCommon Unit Tests
type=page
status=published
~~~~~~
</p>
<iframe class="docFrame" src="tests/test/index.html"/>
<p>
home0.md.listing
title=Project Documentation: CodeCompCommon
date=2019-03-18
type=page
status=published
~~~~~~

2.2.3 Build Steps

To generate a website in build/jbake

  1. Copy the build/reports and build/docs to build/tmp/website
  2. Copy src/jbake to build/tmp/website
  3. Concatenate build/tmp/website/content/home0.md and the project README.md to form build/tmp/website/content/home.md
  4. Run Jbake on build/tmp/website/content/home.md to generate the full website in build/jbake

In build.gradle:

build.gradle.jbake.listing
test.ignoreFailures=true
check.dependsOn htmlDependencyReport

task reports (dependsOn: ['javadoc', 'check']) {
    description 'Generate all reports for this project'
}


task copyJDocs (type: Copy) {
    from 'build/docs'
    into 'build/tmp/website/assets'
    dependsOn 'javadoc'
}

task copyReports (type: Copy) {
    from 'build/reports'
    into 'build/tmp/website/assets'
    dependsOn 'reports'
}

task copyJbakeTemplates (type: Copy) {
    from 'src/jbake'
    into 'build/tmp/website'
}

task buildHomePage (dependsOn: copyJbakeTemplates) {
	 inputs.files ( 'build/tmp/website/content/home0.md', '../README.md')
	 outputs.file ( 'build/tmp/website/content/home.md' )
	 doLast  {
	     outputs.files.singleFile.withOutputStream { out ->
	         for ( file in inputs.files ) file.withInputStream {
		 	     out << it << '\n' }
	    }
	 }
}

jbake {
	 srcDirName = "build/tmp/website"
}

task setupWebsite (dependsOn: ['buildHomePage', 'copyReports', 'copyJDocs']){
}

bake.dependsOn "setupWebsite"

Example

2.3 Publishing to a website

The case studies above will build a website, but we still need to upload that to a web server.

2.3.1 Gradle: ssh solution

Gradle plugin org.hidetake:gradle-ssh-plugin allows you to

A plausible set of Gradle steps:

  1. Create a .zip file of the entire constructed website
  2. Use scp to upload the zip file to the remote server.
  3. Use ssh to issue an unzip command on the remote server.
  4. If necessary, use ssh to issue chmod commands as necessary on the unzipped content.

In build.gradle

plugins {
  id 'org.hidetake.ssh' version '2.9.0'
}

task zipWebsite (type: Zip, dependsOn: 'bake') {   ➀
    archiveFileName = 'website.zip'
    destinationDirectory = file('build')
    from 'build/jbake'
}

remotes {
  webServer {
    host = IP address
    user = userName
    identity = file(ssh-private-key)  ➁
  }
}

task deploy (dependsOn: 'zipWebsite') {
  doLast {
    ssh.run {
      session(remotes.webServer) {
       put from: 'build/website.zip', into: 'websitePath' ➂
       execute 'unzip websitePath/website.zip' -d websitePath➃
      }
    }
  }
}

2.3.2 Gradle: rsync solution

This approach can be used with the rsync-based file drops set up in earlier labs & assignments.

The Java library rsync4j-all provides a Java interface to rsync:


In build.gradle

buildscript {
    /*...*.

    dependencies {
        ⋮
        classpath "com.github.fracpete:rsync4j-all:3.1.2-15"
    }
}

import com.github.fracpete.rsync4j.RSync;  ➀
import com.github.fracpete.processoutput4j.output.ConsoleOutputProcessOutput;

task deployWebsite (dependsOn: "bake") {
    doLast {
        def sourceDir = "build/jbake/";
        def destURL = "destination";  ➁
        RSync rsync = new RSync()
                .source(sourceDir)
                .destination(destURL)
                .recursive(true)
                .archive(true)
                .delete(true)
                .verbose(true)
                .rsh("ssh -o IdentitiesOnly=yes");  ➂
        ConsoleOutputProcessOutput output
                = new ConsoleOutputProcessOutput();
        output.monitor(rsync.builder());
    }
}

Example

2.4 Forges

A software forge is a collection of web services for the support of collaborative software devlopment:


Forge Examples

Among the best known forges are