/* * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. * Copyright (C) 2013 Minecraft Forge * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ package net.minecraftforge.gradle.tasks; import groovy.util.MapEntry; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.xml.bind.DatatypeConverter; import net.minecraftforge.gradle.common.Constants; import org.gradle.api.DefaultTask; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import com.google.common.io.ByteStreams; import com.google.common.io.Files; import com.google.gson.Gson; import com.google.gson.GsonBuilder; public class JenkinsChangelog extends DefaultTask { //@formatter:off @Input Object serverRoot; @Input Object jobName; @Input Object authName; @Input Object authPassword; @Input Object targetBuild; //@formatter:on @OutputFile Object output; private int targetBuildResolved = -1; private String auth = null; @SuppressWarnings("unchecked") @TaskAction public void doTask() throws IOException { if (getAuthName() != null && getAuthPassword() != null) { String raw = getAuthName() + ":" + getAuthPassword(); auth = "Basic " + DatatypeConverter.printBase64Binary(raw.getBytes()); } List<Map<String, Object>> builds = getBuildInfo(); getLatestBuild(builds); StringBuilder out = new StringBuilder(); out.append("Changelog:\r\n"); for (Map<String, Object> build : builds) { int number = ((Double) build.get("number")).intValue(); List<MapEntry> items = (List<MapEntry>) build.get("items"); if (getTargetBuild() > 0 && number > getTargetBuild()) continue; out.append("Build "); out.append(build.get("version") == null ? number : build.get("version")); out.append(':').append('\n'); for (MapEntry entry : items) { String[] lines = ((String) entry.getValue()).trim().split("\n"); if (lines.length == 1) { out.append('\t').append(entry.getKey()).append(": ").append(lines[0]).append('\n'); } else { out.append('\t').append(entry.getKey()).append(':').append('\n'); for (String line : lines) { out.append('\t').append('\t').append(line).append('\n'); } } } out.append('\n'); } File outFile = getOutput(); outFile.getParentFile().mkdirs(); Files.write(out.toString().getBytes(), outFile); } private String read(String url) throws MalformedURLException, IOException { return read(new URL(getServerRoot() + "job/" + getJobName() + url)); } private String read(URL url) throws IOException { URLConnection con = null; con = url.openConnection(); con.setRequestProperty("User-Agent", Constants.USER_AGENT); if (auth != null) { getProject().getLogger().debug(auth); con.addRequestProperty("Authorization", auth); } return new String(ByteStreams.toByteArray(con.getInputStream())); } private String cleanJson(String data, String part) { data = data.replace("," + part + ",", ","); data = data.replace(part + ",", ""); data = data.replace("," + part, ""); data = data.replace("{" + part + "}", "{}"); data = data.replace("[" + part + "]", "[]"); return data; } private static final Gson GSON_FORMATTER = new GsonBuilder().setPrettyPrinting().create(); @SuppressWarnings("unused") private void prettyPrint(Object json) { getLogger().lifecycle(GSON_FORMATTER.toJson(json)); } @SuppressWarnings("unchecked") private List<Map<String, Object>> getBuildInfo() { String data = null; try { boolean versioned = false; data = read("/api/python?tree=allBuilds[result,number,actions[text],changeSet[items[author[fullName],comment]]]");//&pretty=true"); data = data.replace("\"result\":None", "\"result\":\"\""); data = cleanJson(data, "None"); //This kills Gson for some reason data = cleanJson(data, "{}"); //Empty entries, just for sanities sake List<Map<String, Object>> json = (List<Map<String, Object>>) new Gson().fromJson(data, Map.class).get("allBuilds"); Collections.sort(json, new Comparator<Map<String, Object>>() { @Override public int compare(Map<String, Object> o1, Map<String, Object> o2) { return (int) ((Double) o1.get("number") - (Double) o2.get("number")); } }); List<Entry<String, String>> items = new ArrayList<Entry<String, String>>(); Iterator<Map<String, Object>> bitr = json.iterator(); while (bitr.hasNext()) { Map<String, Object> build = bitr.next(); List<Map<String, String>> actions = (List<Map<String, String>>) build.get("actions"); Iterator<Map<String, String>> itr = actions.iterator(); while (itr.hasNext()) { Map<String, String> map = itr.next(); if (!map.containsKey("text") || map.get("text").contains("http") || map.get("text").contains("href=")) { itr.remove(); } } if (actions.size() == 0) { build.put("version", versioned ? ((Double) build.get("number")).intValue() : getProject().getVersion()); versioned = true; } else { build.put("version", actions.get(0).get("text")); } for (Map<String, Object> e : (List<Map<String, Object>>) ((Map<String, Object>) build.get("changeSet")).get("items")) { items.add(new MapEntry(((Map<String, String>) e.get("author")).get("fullName"), e.get("comment"))); } build.put("items", items); if (build.get("result").equals("SUCCESS")) { if (items.size() == 0) bitr.remove(); items = new ArrayList<Entry<String, String>>(); } else { bitr.remove(); } build.remove("result"); build.remove("changeSet"); build.remove("actions"); } //prettyPrint(json); Collections.sort(json, new Comparator<Map<String, Object>>() { @Override public int compare(Map<String, Object> o1, Map<String, Object> o2) { return (int) ((Double) o2.get("number") - (Double) o1.get("number")); } }); return json; } catch (Exception e) { e.printStackTrace(); getLogger().lifecycle(data); } return new ArrayList<Map<String, Object>>(); } @SuppressWarnings("unchecked") private void getLatestBuild(List<Map<String, Object>> builds) { String data = null; try { Object ver = ""; if (builds.size() > 0) { ver = builds.get(0).get("number"); } boolean versioned = false; data = read("/lastBuild/api/python?tree=number,changeSet[items[author[fullName],comment]]");//&pretty=true"); data = cleanJson(data, "None"); //This kills Gson for some reason data = cleanJson(data, "{}"); //Empty entries, just for sanities sake Map<String, Object> build = (Map<String, Object>) new Gson().fromJson(data, Map.class); if (build.get("number").equals(ver)) { return; } build.put("version", versioned ? "Build " + ((Double) build.get("number")).intValue() : getProject().getVersion()); List<Entry<String, String>> items = new ArrayList<Entry<String, String>>(); for (Map<String, Object> e : (List<Map<String, Object>>) ((Map<String, Object>) build.get("changeSet")).get("items")) { items.add(new MapEntry(((Map<String, String>) e.get("author")).get("fullName"), e.get("comment"))); } build.put("items", items); build.remove("result"); build.remove("changeSet"); build.remove("actions"); builds.add(0, build); //prettyPrint(build); } catch (Exception e) { e.printStackTrace(); getLogger().lifecycle(data); } } public String getServerRoot() { return Constants.resolveString(serverRoot); } public void setServerRoot(Object serverRoot) { this.serverRoot = serverRoot; } public String getJobName() { return Constants.resolveString(jobName); } public void setJobName(Object jobName) { this.jobName = jobName; } public String getAuthName() { return Constants.resolveString(authName); } public void setAuthName(Object authName) { this.authName = authName; } public String getAuthPassword() { return Constants.resolveString(authPassword); } public void setAuthPassword(Object authPassword) { this.authPassword = authPassword; } public int getTargetBuild() { if (targetBuildResolved != -1) { return targetBuildResolved; } targetBuildResolved = Integer.MAX_VALUE; if (targetBuild != null) { try { targetBuildResolved = Integer.parseInt(Constants.resolveString(targetBuild)); if (targetBuildResolved <= 0) { targetBuildResolved = Integer.MAX_VALUE; } } catch (NumberFormatException e) { getProject().getLogger().debug("Error reading target build: " + e.getMessage()); } } return targetBuildResolved; } public void setTargetBuild(Object targetBuild) { this.targetBuild = targetBuild; } public File getOutput() { return getProject().file(output); } public void setOutput(Object output) { this.output = output; } }