Java tutorial
/* * Copyright 2016 Igor Maznitsa. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.igormaznitsa.mvngolang; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReentrantLock; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.SystemUtils; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Parameter; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.zeroturnaround.exec.ProcessExecutor; import org.zeroturnaround.exec.ProcessResult; import com.igormaznitsa.meta.annotation.LazyInited; import com.igormaznitsa.meta.annotation.MustNotContainNull; import com.igormaznitsa.meta.common.utils.ArrayUtils; import com.igormaznitsa.meta.common.utils.GetUtils; import com.igormaznitsa.mvngolang.utils.UnpackUtils; import org.apache.maven.project.MavenProject; import com.igormaznitsa.meta.common.utils.StrUtils; import static com.igormaznitsa.meta.common.utils.Assertions.*; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpException; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.routing.HttpRoutePlanner; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.DefaultProxyRoutePlanner; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import org.apache.maven.settings.Proxy; import org.apache.maven.settings.Settings; import com.igormaznitsa.meta.annotation.MayContainNull; import com.igormaznitsa.meta.annotation.ReturnsOriginal; import com.igormaznitsa.mvngolang.utils.ProxySettings; import com.igormaznitsa.mvngolang.utils.WildCardMatcher; public abstract class AbstractGolangMojo extends AbstractMojo { private static final List<String> ALLOWED_SDKARCHIVE_CONTENT_TYPE = Collections.unmodifiableList(Arrays .asList("application/octet-stream", "application/zip", "application/x-tar", "application/x-gzip")); private static final ReentrantLock LOCKER = new ReentrantLock(); private static final String[] BANNER = new String[] { "______ ___ _________ ______", "___ |/ /__ __________ ____/________ / ______ ______________ _", "__ /|_/ /__ | / /_ __ \\ / __ _ __ \\_ / _ __ `/_ __ \\_ __ `/", "_ / / / __ |/ /_ / / / /_/ / / /_/ / /___/ /_/ /_ / / / /_/ / ", "/_/ /_/ _____/ /_/ /_/\\____/ \\____//_____/\\__,_/ /_/ /_/_\\__, /", " /____/", " https://github.com/raydac/mvn-golang", "" }; /** * VERSION, OS, PLATFORM,-OSXVERSION */ public static final String NAME_PATTERN = "go%s.%s-%s%s"; @Parameter(defaultValue = "${project}", readonly = true, required = true) private MavenProject project; @Parameter(defaultValue = "${settings}", readonly = true) protected Settings settings; /** * Use proxy server defined for maven. * * @since 2.1.1 */ @Parameter(name = "useMavenProxy", defaultValue = "false") private boolean useMavenProxy; /** * Parameters of proxy server to be used to estabilish connection to SDK server. * * @since 2.1.1 */ @Parameter(name = "proxy") private ProxySettings proxy; /** * Skip execution of the mojo. * * @since 2.1.2 */ @Parameter(name = "skip", defaultValue = "false") private boolean skip; /** * Ignore error exit code returned by GoLang tool and don't generate any failure. * * @since 2.1.1 */ @Parameter(name = "ignoreErrorExitCode", defaultValue = "false") private boolean ignoreErrorExitCode; /** * Folder to place console logs. * * @since 2.1.1 */ @Parameter(name = "reportsFolder", defaultValue = "${project.build.directory}${file.separator}reports") private String reportsFolder; /** * File to save console out log. If empty then will not be saved. * * @since 2.1.1 */ @Parameter(name = "outLogFile") private String outLogFile; /** * File to save console error log. If empty then will not be saved * * @since 2.1.1 */ @Parameter(name = "errLogFile") private String errLogFile; /** * Base site for SDK download. By default it uses <a href="https://storage.googleapis.com/golang/">https://storage.googleapis.com/golang/</a> */ @Parameter(name = "sdkSite", defaultValue = "https://storage.googleapis.com/golang/") private String sdkSite; /** * Hide ASC banner. */ @Parameter(defaultValue = "true", name = "hideBanner") private boolean hideBanner; /** * Folder to be used to save and unpack loaded SDKs and also keep different info. By default it has value "${user.home}${file.separator}.mvnGoLang" */ @Parameter(defaultValue = "${user.home}${file.separator}.mvnGoLang", name = "storeFolder") private String storeFolder; /** * Folder to be used as $GOPATH. NB! By default it has value "${user.home}${file.separator}.mvnGoLang${file.separator}.go_path" */ @Parameter(defaultValue = "${user.home}${file.separator}.mvnGoLang${file.separator}.go_path", name = "goPath") private String goPath; /** * Folder to be used as $GOARM. NB! By default it has value "${user.home}${file.separator}.mvnGoLang${file.separator}.go_arm" * * @since 2.1.1 */ @Parameter(name = "targetArm") private String targetArm; /** * Folder to be used as $GOBIN. NB! By default it has value "${project.build.directory}". It is possible to disable usage of GOBIN in process through value <b>NONE</b> */ @Parameter(defaultValue = "${project.build.directory}", name = "goBin") private String goBin; /** * The Go SDK version. It plays role if goRoot is undefined. */ @Parameter(name = "goVersion", required = true) private String goVersion; /** * The Go home folder. It can be undefined and in the case the plug-in will make automatic business to find SDK in its cache or download it. */ @Parameter(name = "goRoot") private String goRoot; /** * The Go bootstrap home. */ @Parameter(name = "goRootBootstrap") private String goRootBootstrap; /** * Sub-path to executing go tool in SDK folder. * * @since 1.1.0 */ @Parameter(name = "execSubpath", defaultValue = "bin") private String execSubpath; /** * Go tool to be executed. NB! An Extension for OS will be automatically added. * * @since 1.1.0 */ @Parameter(name = "exec", defaultValue = "go") private String exec; /** * Allows defined text to be printed before execution as warning in to log. */ @Parameter(name = "echoWarn") private String[] echoWarn; /** * Allows defined text to be printed before execution as info into log. */ @Parameter(name = "echo") private String[] echo; /** * Disable loading GoLang SDK through network if it is not found at cache. */ @Parameter(name = "disableSdkLoad", defaultValue = "false") private boolean disableSdkLoad; /** * GoLang source directory. By default <b>${project.build.sourceDirectory}</b> */ @Parameter(defaultValue = "${project.build.sourceDirectory}", name = "sources") private String sources; /** * The Target OS. */ @Parameter(name = "targetOs") private String targetOs; /** * The OS. If it is not defined then plug-in will try figure out the current one. */ @Parameter(name = "os") private String os; /** * The Target architecture. */ @Parameter(name = "targetArch") private String targetArch; /** * The Architecture. If it is not defined then plug-in will try figure out the current one. */ @Parameter(name = "arch") private String arch; /** * Version of OSX to be used during distributive name synthesis. */ @Parameter(name = "osxVersion") private String osxVersion; /** * List of optional build flags. */ @Parameter(name = "buildFlags") private String[] buildFlags; /** * Be verbose in logging. */ @Parameter(name = "verbose", defaultValue = "false") private boolean verbose; /** * Do not delete SDK archive after unpacking. */ @Parameter(name = "keepSdkArchive", defaultValue = "false") private boolean keepSdkArchive; /** * Name of tool to be called instead of standard 'go' tool. */ @Parameter(name = "useGoTool") private String useGoTool; /** * Allows to find environment variable values for $GOROOT, $GOROOT_BOOTSTRAP, $GOOS, $GOARCH, $GOPATH and use them for process.. */ @Parameter(name = "useEnvVars", defaultValue = "false") private boolean useEnvVars; /** * It allows to define key value pairs which will be used as environment variables for started GoLang process. */ @Parameter(name = "env") private Map<?, ?> env; /** * Allows directly define name of SDK archive. If it is not defined then plug-in will try to generate name and find such one in downloaded SDK list.. */ @Parameter(name = "sdkArchiveName") private String sdkArchiveName; /** * Directly defined URL to download SDK. In the case SDK list will not be downloaded and plug-in will try download archive through the link. */ @Parameter(name = "sdkDownloadUrl") private String sdkDownloadUrl; /** * Keep unpacked wrongly SDK folder. */ @Parameter(name = "keepUnarchFolderIfError", defaultValue = "false") private boolean keepUnarchFolderIfError; /** * Allows to define folders which will be added into $GOPATH * * @since 2.0.0 */ @Parameter(name = "addToGoPath") private String[] addToGoPath; @Nullable public String getGoBin() { final String foundInEnvironment = System.getenv("GOBIN"); String result = assertNotNull(this.goBin); if ("NONE".equals(result.trim())) { result = null; } else { if (foundInEnvironment != null && isUseEnvVars()) { result = foundInEnvironment; } } return result; } public boolean isSkip() { return this.skip; } @Nonnull public MavenProject getProject() { return this.project; } public boolean isIgnoreErrorExitCode() { return this.ignoreErrorExitCode; } @Nonnull public Map<?, ?> getEnv() { return GetUtils.ensureNonNull(this.env, Collections.EMPTY_MAP); } @Nullable public String getSdkDownloadUrl() { return this.sdkDownloadUrl; } @Nonnull public String getExecSubpath() { return ensureNoSurroundingSlashes(assertNotNull(this.execSubpath)); } @Nonnull public String getExec() { return ensureNoSurroundingSlashes(assertNotNull(this.exec)); } public boolean isUseEnvVars() { return this.useEnvVars; } public boolean isKeepSdkArchive() { return this.keepSdkArchive; } public boolean isKeepUnarchFolderIfError() { return this.keepUnarchFolderIfError; } @Nullable public String getSdkArchiveName() { return this.sdkArchiveName; } @Nonnull public String getReportsFolder() { return this.reportsFolder; } @Nonnull public String getOutLogFile() { return this.outLogFile; } @Nonnull public String getErrLogFile() { return this.errLogFile; } @Nonnull public String getStoreFolder() { return this.storeFolder; } @Nullable public String getUseGoTool() { return this.useGoTool; } public boolean isVerbose() { return this.verbose; } public boolean isDisableSdkLoad() { return this.disableSdkLoad; } @Nonnull private static String ensureNoSurroundingSlashes(@Nonnull final String str) { String result = str; if (!result.isEmpty() && (result.charAt(0) == '/' || result.charAt(0) == '\\')) { result = result.substring(1); } if (!result.isEmpty() && (result.charAt(result.length() - 1) == '/' || result.charAt(result.length() - 1) == '\\')) { result = result.substring(0, result.length() - 1); } return result; } @Nonnull public String getSdkSite() { return assertNotNull(this.sdkSite); } @Nonnull @MustNotContainNull public String[] getBuildFlags() { return ArrayUtils.joinArrays(GetUtils.ensureNonNull(this.buildFlags, ArrayUtils.EMPTY_STRING_ARRAY), getExtraBuildFlags()); } @Nonnull @MustNotContainNull protected String[] getExtraBuildFlags() { return ArrayUtils.EMPTY_STRING_ARRAY; } @Nonnull public File findGoPath(final boolean ensureExist) throws IOException { LOCKER.lock(); try { final String theGoPath = getGoPath(); if (theGoPath.contains(File.pathSeparator)) { getLog().error( "Detected multiple folder items in the 'goPath' parameter but it must contain only folder!"); throw new IOException("Detected multiple folder items in the 'goPath'"); } final File result = new File(theGoPath); if (ensureExist && !result.isDirectory() && !result.mkdirs()) { throw new IOException("Can't create folder : " + theGoPath); } return result; } finally { LOCKER.unlock(); } } @Nullable public File findGoRootBootstrap(final boolean ensureExist) throws IOException { LOCKER.lock(); try { final String value = getGoRootBootstrap(); File result = null; if (value != null) { result = new File(value); if (ensureExist && !result.isDirectory()) { throw new IOException("Can't find folder for GOROOT_BOOTSTRAP: " + result); } } return result; } finally { LOCKER.unlock(); } } @Nonnull public String getOs() { String result = this.os; if (isSafeEmpty(result)) { if (SystemUtils.IS_OS_WINDOWS) { result = "windows"; } else if (SystemUtils.IS_OS_LINUX) { result = "linux"; } else if (SystemUtils.IS_OS_FREE_BSD) { result = "freebsd"; } else { result = "darwin"; } } return result; } @Nonnull public String getArch() { String result = this.arch; if (isSafeEmpty(result)) { result = investigateArch(); } return result; } @Nonnull public String getGoPath() { final String foundInEnvironment = System.getenv("GOPATH"); String result = assertNotNull(this.goPath); if (foundInEnvironment != null && isUseEnvVars()) { result = foundInEnvironment; } return result; } @Nullable public String getTargetArm() { String result = this.targetArm; if (isSafeEmpty(result) && isUseEnvVars()) { result = System.getenv("GOARM"); } return result; } @Nullable public String getTargetOS() { String result = this.targetOs; if (isSafeEmpty(result) && isUseEnvVars()) { result = System.getenv("GOOS"); } return result; } @Nullable public String getTargetArch() { String result = this.targetArch; if (isSafeEmpty(result) && isUseEnvVars()) { result = System.getenv("GOARCH"); } return result; } public boolean isUseMavenProxy() { return this.useMavenProxy; } @Nullable public ProxySettings getProxy() { return this.proxy; } @Nullable public String getOSXVersion() { return this.osxVersion; } @Nonnull public String getGoVersion() { return this.goVersion; } @Nullable public String getGoRoot() { String result = this.goRoot; if (isSafeEmpty(result) && isUseEnvVars()) { result = System.getenv("GOROOT"); } return result; } @Nullable public String getGoRootBootstrap() { String result = this.goRootBootstrap; if (isSafeEmpty(result) && isUseEnvVars()) { result = System.getenv("GOROOT_BOOTSTRAP"); } return result; } @Nonnull public File getSources(final boolean ensureExist) throws IOException { final File result = new File(this.sources); if (ensureExist && !result.isDirectory()) { throw new IOException("Can't find GoLang project sources : " + result); } return result; } @LazyInited private CloseableHttpClient httpClient; @Nonnull private synchronized HttpClient getHttpClient(@Nullable final ProxySettings proxy) { if (this.httpClient == null) { final HttpClientBuilder builder = HttpClients.custom(); if (proxy != null) { if (proxy.hasCredentials()) { final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(new AuthScope(proxy.host, proxy.port), new UsernamePasswordCredentials(proxy.username, proxy.password)); builder.setDefaultCredentialsProvider(credentialsProvider); getLog().debug( String.format("Credentials provider has been created for proxy (username : %s): %s", proxy.username, proxy.toString())); } final String[] ignoreForAddresses = proxy.nonProxyHosts == null ? new String[0] : proxy.nonProxyHosts.split("\\|"); if (ignoreForAddresses.length > 0) { final WildCardMatcher[] matchers = new WildCardMatcher[ignoreForAddresses.length]; for (int i = 0; i < ignoreForAddresses.length; i++) { matchers[i] = new WildCardMatcher(ignoreForAddresses[i]); } final HttpRoutePlanner routePlanner = new DefaultProxyRoutePlanner( new HttpHost(proxy.host, proxy.port, proxy.protocol)) { @Override @Nonnull public HttpRoute determineRoute(@Nonnull final HttpHost host, @Nonnull final HttpRequest request, @Nonnull final HttpContext context) throws HttpException { final String hostName = host.getHostName(); for (final WildCardMatcher m : matchers) { if (m.match(hostName)) { getLog().debug("Ignoring proxy for host : " + hostName); return new HttpRoute(host); } } return super.determineRoute(host, request, context); } }; builder.setRoutePlanner(routePlanner); getLog().debug( "Route planner tuned to ignore proxy for addresses : " + Arrays.toString(matchers)); } } builder.setUserAgent("mvn-golang-wrapper-agent/1.0"); this.httpClient = builder.build(); } return this.httpClient; } @Nullable private ProxySettings extractProxySettings() { final ProxySettings result; if (this.isUseMavenProxy()) { final Proxy activeMavenProxy = this.settings == null ? null : this.settings.getActiveProxy(); result = activeMavenProxy == null ? null : new ProxySettings(activeMavenProxy); getLog().debug("Detected maven proxy : " + result); } else { result = this.proxy; if (result != null) { getLog().debug("Defined proxy : " + result); } } return result; } @ReturnsOriginal @Nonnull private RequestConfig.Builder processRequestConfig(@Nullable final ProxySettings proxySettings, @Nonnull final RequestConfig.Builder config) { if (proxySettings != null) { final HttpHost proxyHost = new HttpHost(proxySettings.host, proxySettings.port, proxySettings.protocol); config.setProxy(proxyHost); } return config; } @Nonnull private String loadGoLangSdkList(@Nullable final ProxySettings proxySettings) throws IOException { final String sdksite = getSdkSite(); getLog().warn("Loading list of available GoLang SDKs from " + sdksite); final HttpGet get = new HttpGet(sdksite); final RequestConfig config = processRequestConfig(proxySettings, RequestConfig.custom()).build(); get.setConfig(config); get.addHeader("Accept", "application/xml"); try { final HttpResponse response = getHttpClient(proxySettings).execute(get); final StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() == HttpStatus.SC_OK) { final String content = EntityUtils.toString(response.getEntity()); getLog().info("GoLang SDK list has been loaded successfuly"); getLog().debug(content); return content; } else { throw new IOException(String.format("Can't load list of SDKs from %s : %d %s", sdksite, statusLine.getStatusCode(), statusLine.getReasonPhrase())); } } finally { get.releaseConnection(); } } @Nonnull private Document convertSdkListToDocument(@Nonnull final String sdkListAsString) throws IOException { try { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = factory.newDocumentBuilder(); return builder.parse(new InputSource(new StringReader(sdkListAsString))); } catch (ParserConfigurationException ex) { getLog().error("Can't configure XML parser", ex); throw new IOException("Can't configure XML parser", ex); } catch (SAXException ex) { getLog().error("Can't parse document", ex); throw new IOException("Can't parse document", ex); } catch (IOException ex) { getLog().error("Unexpected IOException", ex); throw new IOException("Unexpected IOException", ex); } } @LazyInited private ByteArrayOutputStream consoleErrBuffer; @LazyInited private ByteArrayOutputStream consoleOutBuffer; private void printEcho() { if (this.echoWarn != null) { for (final String s : this.echoWarn) { getLog().warn(s); } } if (this.echo != null) { for (final String s : this.echo) { getLog().info(s); } } } private static void deleteFileIfExists(@Nonnull final File file) throws IOException { if (file.isFile() && !file.delete()) { throw new IOException("Can't delete file : " + file); } } protected void logOptionally(@Nonnull final String message) { if (getLog().isDebugEnabled() || this.verbose) { getLog().info(message); } } private void initConsoleBuffers() { getLog().debug("Initing console out and console err buffers"); this.consoleErrBuffer = new ByteArrayOutputStream(); this.consoleOutBuffer = new ByteArrayOutputStream(); } @Nonnull private File unpackArchToFolder(@Nonnull final File archiveFile, @Nonnull final String folderInArchive, @Nonnull final File destinationFolder) throws IOException { getLog().info(String.format("Unpacking archive %s to folder %s", archiveFile.getName(), destinationFolder.getName())); boolean detectedError = true; try { final int unpackedFileCounter = UnpackUtils.unpackFileToFolder(getLog(), folderInArchive, archiveFile, destinationFolder, true); if (unpackedFileCounter == 0) { throw new IOException( "Couldn't find folder '" + folderInArchive + "' in archive or the archive is empty"); } else { getLog().info("Unpacked " + unpackedFileCounter + " file(s)"); } detectedError = false; } finally { if (detectedError && !isKeepUnarchFolderIfError()) { logOptionally("Deleting folder because error during unpack : " + destinationFolder); FileUtils.deleteQuietly(destinationFolder); } } return destinationFolder; } private static boolean isSafeEmpty(@Nullable final String value) { return value == null || value.isEmpty(); } @Nonnull private static String extractExtensionOfArchive(@Nonnull final String archiveName) { final String lcName = archiveName; final String result; if (lcName.endsWith(".tar.gz")) { result = archiveName.substring(archiveName.length() - "tar.gz".length()); } else { result = FilenameUtils.getExtension(archiveName); } return result; } @Nonnull private File loadSDKAndUnpackIntoCache(@Nullable final ProxySettings proxySettings, @Nonnull final File cacheFolder, @Nonnull final String baseSdkName) throws IOException { final File sdkFolder = new File(cacheFolder, baseSdkName); final String predefinedLink = getSdkDownloadUrl(); final File archiveFile; final String linkForDownloading; if (isSafeEmpty(predefinedLink)) { logOptionally("There is not any predefined SDK URL"); final String sdkFileName = findSdkArchiveFileName(proxySettings, baseSdkName); archiveFile = new File(cacheFolder, sdkFileName); linkForDownloading = getSdkSite() + sdkFileName; } else { final String extension = extractExtensionOfArchive(assertNotNull(predefinedLink)); archiveFile = new File(cacheFolder, baseSdkName + '.' + extension); linkForDownloading = predefinedLink; logOptionally("Using predefined URL to download SDK : " + linkForDownloading); logOptionally("Detected extension of archive : " + extension); } final HttpGet methodGet = new HttpGet(linkForDownloading); final RequestConfig config = processRequestConfig(proxySettings, RequestConfig.custom()).build(); methodGet.setConfig(config); boolean errorsDuringLoading = true; try { if (!archiveFile.isFile()) { getLog().warn("Loading SDK archive with URL : " + linkForDownloading); final HttpResponse response = getHttpClient(proxySettings).execute(methodGet); final StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() != HttpStatus.SC_OK) { throw new IOException(String.format("Can't load SDK archive from %s : %d %s", linkForDownloading, statusLine.getStatusCode(), statusLine.getReasonPhrase())); } final HttpEntity entity = response.getEntity(); final Header contentType = entity.getContentType(); if (!ALLOWED_SDKARCHIVE_CONTENT_TYPE.contains(contentType.getValue())) { throw new IOException("Unsupported content type : " + contentType.getValue()); } final InputStream inStream = entity.getContent(); getLog().info("Downloading SDK archive into file : " + archiveFile); FileUtils.copyInputStreamToFile(inStream, archiveFile); getLog().info("Archived SDK has been succesfully downloaded, its size is " + (archiveFile.length() / 1024L) + " Kb"); inStream.close(); } else { getLog().info("Archive file of SDK has been found in the cache : " + archiveFile); } errorsDuringLoading = false; return unpackArchToFolder(archiveFile, "go", sdkFolder); } finally { methodGet.releaseConnection(); if (errorsDuringLoading || !this.isKeepSdkArchive()) { logOptionally("Deleting archive : " + archiveFile + (errorsDuringLoading ? " (because error during loading)" : "")); deleteFileIfExists(archiveFile); } else { logOptionally("Archive file is kept for special flag : " + archiveFile); } } } @Nonnull private String extractSDKFileName(@Nonnull final Document doc, @Nonnull final String sdkBaseName, @Nonnull @MustNotContainNull final String[] allowedExtensions) throws IOException { getLog().debug("Looking for SDK started with base name : " + sdkBaseName); final Set<String> variants = new HashSet<String>(); for (final String ext : allowedExtensions) { variants.add(sdkBaseName + '.' + ext); } final List<String> listedSdk = new ArrayList<String>(); final Element root = doc.getDocumentElement(); if ("ListBucketResult".equals(root.getTagName())) { final NodeList list = root.getElementsByTagName("Contents"); for (int i = 0; i < list.getLength(); i++) { final Element element = (Element) list.item(i); final NodeList keys = element.getElementsByTagName("Key"); if (keys.getLength() > 0) { final String text = keys.item(0).getTextContent(); if (variants.contains(text)) { logOptionally("Detected compatible SDK in the SDK list : " + text); return text; } else { listedSdk.add(text); } } } getLog().error("Can't find any SDK to be used as " + sdkBaseName); getLog().error("GoLang list contains listed SDKs"); getLog().error(".................................................."); for (final String s : listedSdk) { getLog().error(s); } throw new IOException("Can't find SDK : " + sdkBaseName); } else { throw new IOException("It is not a ListBucket file [" + root.getTagName() + ']'); } } @Nonnull private String findSdkArchiveFileName(@Nullable final ProxySettings proxySettings, @Nonnull final String sdkBaseName) throws IOException { String result = getSdkArchiveName(); if (isSafeEmpty(result)) { final Document parsed = convertSdkListToDocument(loadGoLangSdkList(proxySettings)); result = extractSDKFileName(parsed, sdkBaseName, new String[] { "tar.gz", "zip" }); } else { getLog().info("SDK archive name is predefined : " + result); } return GetUtils.ensureNonNullStr(result); } private void warnIfContainsUC(@Nonnull final String message, @Nonnull final String str) { boolean detected = false; for (final char c : str.toCharArray()) { if (Character.isUpperCase(c)) { detected = true; break; } } if (detected) { getLog().warn(message + " : " + str); } } @Nonnull private File findGoRoot(@Nullable final ProxySettings proxySettings) throws IOException, MojoFailureException { final File result; LOCKER.lock(); try { final String predefinedGoRoot = this.getGoRoot(); if (isSafeEmpty(predefinedGoRoot)) { final File cacheFolder = new File(this.storeFolder); if (!cacheFolder.isDirectory()) { logOptionally("Making SDK cache folder : " + cacheFolder); if (!cacheFolder.mkdirs()) { throw new IOException("Can't create folder " + cacheFolder); } } final String definedOsxVersion = this.getOSXVersion(); final String sdkVersion = this.getGoVersion(); if (isSafeEmpty(sdkVersion)) { throw new MojoFailureException("GoLang SDK version is not defined!"); } final String sdkBaseName = String.format(NAME_PATTERN, sdkVersion, this.getOs(), this.getArch(), isSafeEmpty(definedOsxVersion) ? "" : "-" + definedOsxVersion); warnIfContainsUC("Prefer usage of lower case chars only for SDK base name", sdkBaseName); final File alreadyCached = new File(cacheFolder, sdkBaseName); if (alreadyCached.isDirectory()) { logOptionally("Cached SDK detected : " + alreadyCached); result = alreadyCached; } else { if (this.disableSdkLoad) { throw new MojoFailureException( "Can't find " + sdkBaseName + " in the cache but loading is directly disabled"); } result = loadSDKAndUnpackIntoCache(proxySettings, cacheFolder, sdkBaseName); } } else { logOptionally("Detected predefined SDK root folder : " + predefinedGoRoot); result = new File(predefinedGoRoot); if (!result.isDirectory()) { throw new MojoFailureException("Predefined SDK root is not a directory : " + result); } } } finally { LOCKER.unlock(); } return result; } @Nonnull private static String investigateArch() { final String arch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); if (arch.contains("arm")) { return "arm"; } if (arch.equals("386") || arch.equals("i386") || arch.equals("x86")) { return "386"; } return "amd64"; } private void printBanner() { for (final String s : BANNER) { getLog().info(s); } } public boolean isHideBanner() { return this.hideBanner; } protected boolean doesNeedOneMoreAttempt(@Nonnull final ProcessResult result, @Nonnull final String consoleOut, @Nonnull final String consoleErr) throws IOException, MojoExecutionException { return false; } @Override public final void execute() throws MojoExecutionException, MojoFailureException { if (this.isSkip()) { getLog().info("Execution is skipped by flag"); } else { if (!isHideBanner()) { printBanner(); } printEcho(); final ProxySettings proxySettings = extractProxySettings(); beforeExecution(proxySettings); boolean error = false; try { int iterations = 0; while (true) { final ProcessExecutor executor = prepareExecutor(proxySettings); if (executor == null) { logOptionally("The Mojo should not be executed"); break; } final ProcessResult result = executor.executeNoTimeout(); final int resultCode = result.getExitValue(); error = resultCode != 0 && !isIgnoreErrorExitCode(); iterations++; final String outLog = extractOutAsString(); final String errLog = extractErrorOutAsString(); if (this.processConsoleOut(resultCode, outLog, errLog)) { printLogs(outLog, errLog); } if (doesNeedOneMoreAttempt(result, outLog, errLog)) { if (iterations > 10) { throw new MojoExecutionException( "Too many iterations detected, may be some loop and bug at mojo " + this.getClass().getName()); } getLog().warn("Make one more attempt..."); } else { if (!isIgnoreErrorExitCode()) { assertProcessResult(result); } break; } } } catch (IOException ex) { error = true; throw new MojoExecutionException(ex.getMessage(), ex); } catch (InterruptedException ex) { error = true; } finally { afterExecution(null, error); } } } public void beforeExecution(@Nullable final ProxySettings proxySettings) throws MojoFailureException, MojoExecutionException { } public void afterExecution(@Nullable final ProxySettings proxySettings, final boolean error) throws MojoFailureException, MojoExecutionException { } public boolean enforcePrintOutput() { return false; } @Nonnull private String extractOutAsString() { return new String(this.consoleOutBuffer.toByteArray(), Charset.defaultCharset()); } @Nonnull private String extractErrorOutAsString() { return new String(this.consoleErrBuffer.toByteArray(), Charset.defaultCharset()); } protected void printLogs(@Nonnull final String outLog, @Nonnull final String errLog) { if ((enforcePrintOutput() || getLog().isDebugEnabled()) && !outLog.isEmpty()) { getLog().info(""); getLog().info("---------Exec.Out---------"); for (final String str : outLog.split("\n")) { getLog().info(StrUtils.trimRight(str)); } getLog().info(""); } if (!errLog.isEmpty()) { getLog().error(""); getLog().error("---------Exec.Err---------"); for (final String str : errLog.split("\n")) { getLog().error(StrUtils.trimRight(str)); } getLog().error(""); } } private void assertProcessResult(@Nonnull final ProcessResult result) throws MojoFailureException { final int code = result.getExitValue(); if (code != 0) { throw new MojoFailureException("Process exit code : " + code); } } public boolean isSourceFolderRequired() { return false; } public boolean isMojoMustNotBeExecuted() throws MojoFailureException { try { return isSourceFolderRequired() && !this.getSources(false).isDirectory(); } catch (IOException ex) { throw new MojoFailureException("Can't check source folder", ex); } } @Nonnull @MustNotContainNull public abstract String[] getTailArguments(); @Nonnull @MustNotContainNull public String[] getOptionalExtraTailArguments() { return ArrayUtils.EMPTY_STRING_ARRAY; } @Nonnull public String makeExecutableFileSubpath() { return getExecSubpath() + File.separatorChar + getExec(); } @Nonnull public abstract String getGoCommand(); @Nonnull @MustNotContainNull public abstract String[] getCommandFlags(); private void addEnvVar(@Nonnull final ProcessExecutor executor, @Nonnull final String name, @Nonnull final String value) { logOptionally(" $" + name + " = " + value); executor.environment(name, value); } @Nonnull protected static String adaptExecNameForOS(@Nonnull final String execName) { return execName + (SystemUtils.IS_OS_WINDOWS ? ".exe" : ""); } @Nonnull private static String getPathToFolder(@Nonnull final String path) { String text = path; if (!text.endsWith("/") && !text.endsWith("\\")) { text = text + File.separatorChar; } return text; } @Nonnull private static String getPathToFolder(@Nonnull final File path) { return getPathToFolder(path.getAbsolutePath()); } @Nullable private ProcessExecutor prepareExecutor(@Nullable final ProxySettings proxySettings) throws IOException, MojoFailureException { initConsoleBuffers(); final String execNameAdaptedForOs = adaptExecNameForOS(makeExecutableFileSubpath()); final File detectedRoot = findGoRoot(proxySettings); final String gobin = getGoBin(); final File gopath = findGoPath(true); if (isMojoMustNotBeExecuted()) { return null; } final String toolName = FilenameUtils .normalize(GetUtils.ensureNonNull(getUseGoTool(), execNameAdaptedForOs)); final File executableFileInPathOrRoot = new File(getPathToFolder(detectedRoot) + toolName); final File executableFileInBin = gobin == null ? null : new File(getPathToFolder(gobin) + adaptExecNameForOS(getExec())); final File[] exeVariants = new File[] { executableFileInBin, executableFileInPathOrRoot }; final File foundExecutableTool = findExisting(exeVariants); if (foundExecutableTool == null) { throw new MojoFailureException("Can't find executable file : " + Arrays.toString(exeVariants)); } else { logOptionally("Executable file detected : " + foundExecutableTool); } final List<String> commandLine = new ArrayList<String>(); commandLine.add(foundExecutableTool.getAbsolutePath()); final String gocommand = getGoCommand(); if (!gocommand.isEmpty()) { commandLine.add(getGoCommand()); } for (final String s : getCommandFlags()) { commandLine.add(s); } for (final String s : getBuildFlags()) { commandLine.add(s); } for (final String s : getTailArguments()) { commandLine.add(s); } for (final String s : getOptionalExtraTailArguments()) { commandLine.add(s); } final StringBuilder cli = new StringBuilder(); int index = 0; for (final String s : commandLine) { if (cli.length() > 0) { cli.append(' '); } if (index == 0) { cli.append(execNameAdaptedForOs); } else { cli.append(s); } index++; } getLog().info(String.format("Prepared command line : %s", cli.toString())); final ProcessExecutor result = new ProcessExecutor(commandLine); final File sourcesFile = getSources(isSourceFolderRequired()); logOptionally("GoLang project sources folder : " + sourcesFile); if (sourcesFile.isDirectory()) { result.directory(sourcesFile); } logOptionally(""); logOptionally("....Environment vars...."); addEnvVar(result, "GOROOT", detectedRoot.getAbsolutePath()); addEnvVar(result, "GOPATH", preparePath(gopath.getAbsolutePath(), getExtraPathToAddToGoPathBeforeSources(), removeSrcFolderAtEndIfPresented(sourcesFile.getAbsolutePath()), getExtraPathToAddToGoPathToEnd())); if (gobin == null) getLog().warn("GOBIN is disabled by direct order"); else addEnvVar(result, "GOBIN", gobin); final String trgtOs = this.getTargetOS(); final String trgtArch = this.getTargetArch(); final String trgtArm = this.getTargetArm(); if (trgtOs != null) { addEnvVar(result, "GOOS", trgtOs); } if (trgtArm != null) { addEnvVar(result, "GOARM", trgtArm); } if (trgtArch != null) { addEnvVar(result, "GOARCH", trgtArch); } final File gorootbootstrap = findGoRootBootstrap(true); if (gorootbootstrap != null) { addEnvVar(result, "GOROOT_BOOTSTRAP", gorootbootstrap.getAbsolutePath()); } String thePath = GetUtils.ensureNonNullStr(System.getenv("PATH")); thePath = preparePath(thePath, (detectedRoot + File.separator + getExecSubpath()), gobin); addEnvVar(result, "PATH", thePath); for (final Map.Entry<?, ?> record : getEnv().entrySet()) { addEnvVar(result, record.getKey().toString(), record.getValue().toString()); } logOptionally("........................"); result.redirectOutput(this.consoleOutBuffer); result.redirectError(this.consoleErrBuffer); return result; } @Nullable protected static File findExisting(@Nonnull @MayContainNull final File... files) { File result = null; for (final File f : files) { if (f != null && f.isFile()) { result = f; break; } } return result; } @Nonnull protected String getExtraPathToAddToGoPathToEnd() { return ""; } @Nonnull protected String getExtraPathToAddToGoPathBeforeSources() { String result = ""; if (this.addToGoPath != null) { result = preparePath(this.addToGoPath); } return result; } @Nonnull private static String removeSrcFolderAtEndIfPresented(@Nonnull final String text) { String result = text; if (text.endsWith("/src") || text.endsWith("\\src")) { result = text.substring(0, text.length() - 4); } return result; } @Nonnull private static String preparePath(@Nonnull @MayContainNull final String... paths) { final StringBuilder result = new StringBuilder(); for (final String s : paths) { if (s != null && !s.isEmpty()) { if (result.length() > 0) { result.append(SystemUtils.IS_OS_WINDOWS ? ';' : ':'); } result.append(s); } } return result.toString(); } protected boolean processConsoleOut(final int exitCode, @Nonnull final String out, @Nonnull final String err) throws MojoFailureException, MojoExecutionException { final File reportsFolderFile = new File(this.getReportsFolder()); final String fileOut = this.getOutLogFile(); final String fileErr = this.getErrLogFile(); final File fileToWriteOut = fileOut == null || fileOut.trim().isEmpty() ? null : new File(reportsFolderFile, fileOut); final File fileToWriteErr = fileErr == null || fileErr.trim().isEmpty() ? null : new File(reportsFolderFile, fileErr); if (fileToWriteOut != null) { getLog().debug("Reports folder : " + reportsFolderFile); if (!reportsFolderFile.isDirectory() && !reportsFolderFile.mkdirs()) { throw new MojoExecutionException("Can't create folder for console logs : " + reportsFolderFile); } try { getLog().debug("Writing out console log : " + fileToWriteErr); FileUtils.write(fileToWriteOut, out, "UTF-8"); } catch (IOException ex) { throw new MojoExecutionException("Can't save console output log into file : " + fileToWriteOut, ex); } } if (fileToWriteErr != null) { getLog().debug("Reports folder : " + reportsFolderFile); if (!reportsFolderFile.isDirectory() && !reportsFolderFile.mkdirs()) { throw new MojoExecutionException("Can't create folder for console logs : " + reportsFolderFile); } try { getLog().debug("Writing error console log : " + fileToWriteErr); FileUtils.write(fileToWriteErr, err, "UTF-8"); } catch (IOException ex) { throw new MojoExecutionException("Can't save console error log into file : " + fileToWriteErr, ex); } } return true; } }