We’ve already looked JUnit, which can be used to generate test reports like this one or this one.
This is generated in ant via the junitreport
task:
<project name="code2html" basedir="." default="build">
<record name="ant.log" action="start" append="false" />
<taskdef classpath="JFlex.jar" classname="JFlex.anttask.JFlexTask" name="jflex" />
<echo>loading build-${os.name}.paths</echo>
<include file="build-${os.name}.paths"/>
<target name="generateSource">
<mkdir dir="src/main/java"/>
<jflex file="src/main/jflex/code2html.flex"
destdir="src/main/java"/>
<jflex file="src/main/jflex/code2tex.flex"
destdir="src/main/java"/>
<jflex file="src/main/jflex/list2html.flex"
destdir="src/main/java"/>
<jflex file="src/main/jflex/list2tex.flex"
destdir="src/main/java"/>
</target>
<target name="compile" depends="generateSource">
<mkdir dir="target/classes"/>
<javac srcdir="src/main/java" destdir="target/classes"
source="1.6" includeantruntime="false"/>
</target>
<target name="compile-tests" depends="compile">
<mkdir dir="target/test-classes"/>
<javac srcdir="src/test/java" destdir="target/test-classes"
source="1.6" includeantruntime="false">
<classpath refid="testCompilationPath"/>
</javac>
</target>
<target name="test" depends="compile-tests">
<property name="mypath" refid="testExecutionPath"/>
<echo>testExecutioPath is ${mypath}</echo>
<echoproperties/>
<mkdir dir="target/test-results/details"/>
<junit printsummary="yes"
haltonfailure="yes" fork="no"
>
<classpath refid="testExecutionPath"/>
<formatter type="xml"/>
<batchtest todir="target/test-results/details">
<fileset dir="target/test-classes">
<include name="**/*Test*.class"/>
</fileset>
</batchtest>
</junit>
<junitreport todir="target/test-results">
<fileset dir="target/test-results/details">
<include name="TEST-*.xml"/>
</fileset>
<report format="frames" todir="target/test-results/html"/>
</junitreport>
</target>
<target name="build" depends="test">
<jar destfile="codeAnnotation.jar" basedir="target/classes">
<manifest>
<attribute name="Main-Class"
value="edu.odu.cs.code2html.Code2HTML"/>
</manifest>
</jar>
</target>
<target name="clean">
<delete dir="target"/>
</target>
</project>
or in Gradle via the java
plugin.
Many tools that we will cover later for analyzing code can produce useful (or at least, impressive) documentation as a side effect.
Configuration managers generate reports about the dependencies among the software components.
Examples:
In general, websites can be “manually” constructed, file by file, or any of a number of site-builder tools can be employed to create large numbers of pages that share a common look-and-feel.
An innovation of Maven was to consider construction of project websites as a part of the automated build.
In Gradle this can be managed by custom tasks or by various plugins.
Let’s look at the process of building a simple website that provides
We can consider adding other content in later lessons.
After a build, we can construct a website in build/reports
by
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
.
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.
<!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>
After a build, we can construct a website in build/reports
by
build/doc/
into build/website
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'
}
➀ Don’t want the build to stop just because some unit tests are failing
➁ Force the generation of the configuration report whenever we build the check
target/task.
➂ Here’s our new target, buildSite
.
It has no actions of its own, but the depedencies force everything else to happen.
➃ Here’s step 1 of our process: copying the docs
into the website
src/html
into the websiteFor 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
Content files can contain HTML, or Markdown, or a number of other text forms.
Each has a small amount of metadata at the top. Among other things, this identifies the starting template used to render that content.
Templates are HTML-like snippets that indicate where to inject content and what other templates to include.
A typical “page” template may load separate templates for headers, footers, navigation bars, menus, etc.
Gradle has a jbake
plugin that adds a bake
task to generate a website.
src/jbake
build/jbake
buildscript {
⋮
dependencies {
⋮
classpath "org.jbake:jbake-gradle-plugin:5.0.0"
}
}
⋮
apply plugin: 'org.jbake.site'
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.
page.ftl
Templates:
<#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">
${content.body}
.#include
s the header, menu, and footer templates.<!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">
<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>
</div>
<div id="footer">
<div class="container">
<p class="muted credit">© 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:
title=CodeCompCommon Unit Tests
type=page
status=published
~~~~~~
</p>
<iframe class="docFrame" src="tests/test/index.html"/>
<p>
title=Project Documentation: CodeCompCommon
date=2019-03-18
type=page
status=published
~~~~~~
To generate a website in build/jbake
build/reports
and build/docs
to build/tmp/website
src/jbake
to build/tmp/website
build/tmp/website/content/home0.md
and the project README.md
to form build/tmp/website/content/home.md
Jbake
on build/tmp/website/content/home.md
to generate the full website in build/jbake
In build.gradle
:
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"
bake
which runs JBake
and depends on setupWebsite
setupWebsite
carries out most of our process
buildHomePage
, which carries out the concatentationThe case studies above will build a website, but we still need to upload that to a web server.
Gradle plugin org.hidetake:gradle-ssh-plugin
allows you to
scp
to upload or download one file at a time from/to a remote server.ssh
to issue commands to a remote server.A plausible set of Gradle steps:
.zip
file of the entire constructed websitescp
to upload the zip file to the remote server.ssh
to issue an unzip
command on the remote server.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➃
}
}
}
}
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
:
rsync
command is present.ssh
and rsync
executables.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());
}
}
rSync4j
library, loaded in the dependencies section above, is not a plugin. It’s jsut a library. But we can use Java libraries in Gradle (Groovy) pretty much like we use them in Java.ssh
URL for the website destination here.A software forge is a collection of web services for the support of collaborative software devlopment:
Project web sites
Networked access to version control
Communications (e.g., messaging, wikis, announcements)
Bug reporting and tracking
Project personnel management
Forge Examples
Among the best known forges are
the original, SourceForge, (1999)
Google Code, (2006)
GitHub, (2008)