Package au.com.rayh

Source Code of au.com.rayh.XCodeBuilder$DescriptorImpl

package au.com.rayh;
import com.google.common.collect.Lists;
import hudson.EnvVars;
import hudson.Launcher;
import hudson.Extension;
import hudson.FilePath;
import hudson.util.FormValidation;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.AbstractProject;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.QueryParameter;

import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;

/**
* @author Ray Hilton
*/
public class XCodeBuilder extends Builder {
    private Boolean buildIpa;
    private Boolean cleanBeforeBuild;
    private Boolean updateBuildNumber;
    private String configuration;
    private String overrideMarketingNumber;
    private String target;
    private String sdk;
    private String xcodeProjectPath;
    private String xcodeProjectFile;
    private String embeddedProfileFile;
    private String versionNumberPattern;

    // Fields in config.jelly must match the parameter names in the "DataBoundConstructor"
    @DataBoundConstructor
    public XCodeBuilder(Boolean buildIpa, Boolean cleanBeforeBuild, Boolean updateBuildNumber, String configuration, String target, String sdk, String xcodeProjectPath, String xcodeProjectFile, String embeddedProfileFile, String versionNumberPattern, String overrideMarketingNumber) {
        this.buildIpa = buildIpa;
        this.sdk = sdk;
        this.target = target;
        this.cleanBeforeBuild = cleanBeforeBuild;
        this.updateBuildNumber = updateBuildNumber;
        this.overrideMarketingNumber = overrideMarketingNumber;
        this.configuration = configuration;
        this.xcodeProjectPath = xcodeProjectPath;
        this.xcodeProjectFile = xcodeProjectFile;
        this.embeddedProfileFile = embeddedProfileFile;
        this.versionNumberPattern = versionNumberPattern;
    }

    public String getVersionNumberPattern() {
        return versionNumberPattern;
    }
    public String getSdk() {
        return sdk;
    }

    public String getTarget() {
        return target;
    }

    public String getConfiguration() {
        return configuration;
    }

    public Boolean getBuildIpa() {
        return buildIpa;
    }

    public Boolean getCleanBeforeBuild() {
        return cleanBeforeBuild;
    }

    public Boolean getUpdateBuildNumber() {
        return updateBuildNumber;
    }

    public String getOverrideMarketingNumber() {
        return overrideMarketingNumber;
    }

    public String getXcodeProjectPath() {
        return xcodeProjectPath;
    }

    public String getXcodeProjectFile() {
        return xcodeProjectFile;
    }

    public String getEmbeddedProfileFile() {
        return embeddedProfileFile;
    }

    @Override
    public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
        EnvVars envs = build.getEnvironment(listener);
        FilePath projectRoot = build.getProject().getWorkspace();

        // check that the configured tools exist
        if(!new FilePath(projectRoot.getChannel(), getDescriptor().xcodebuildPath()).exists()) {
            listener.fatalError("Cannot find xcodebuild with the configured path {0}", getDescriptor().xcodebuildPath());
        }
        if(!new FilePath(projectRoot.getChannel(), getDescriptor().agvtoolPath()).exists()) {
            listener.fatalError("Cannot find agvtool with the configured path {0}", getDescriptor().agvtoolPath());
        }

        // Set the working directory
        if(!StringUtils.isEmpty(xcodeProjectPath)) {
            projectRoot = projectRoot.child(xcodeProjectPath);
        }
        listener.getLogger().println("Working directory is " + projectRoot);
        FilePath buildDirectory = projectRoot.child("build").child(configuration + "-iphoneos");

        // XCode Version
        int returnCode = launcher.launch().envs(envs).cmds(getDescriptor().xcodebuildPath(), "-version").stdout(listener).pwd(projectRoot).join();
        if(returnCode>0) return false;

        // Unlock keychain
//        if(!StringUtils.isEmpty(keychainPassword)) {
//            launcher.launch().envs(envs).cmds("security", "unlock-keychain", "-p", keychainPassword);
//        }

        // Set build number
        String artifactVersion = String.valueOf(build.getNumber());
        String versionNumber = artifactVersion;
        if(!StringUtils.isEmpty(getVersionNumberPattern())) {
             versionNumber = getVersionNumberPattern().replaceAll("\\{BUILD_NUMBER\\}", artifactVersion);
        }
       
        if(updateBuildNumber) {
            listener.getLogger().println("Updating version number (CFBundleVersion) to " + versionNumber);
            //ByteArrayOutputStream output = new ByteArrayOutputStream();
            //returnCode = launcher.launch().envs(envs).cmds("agvtool", "mvers", "-terse1").stdout(output).pwd(projectRoot).join();
            //if(returnCode>0) {

            //} else {
            //    String marketingVersionNumber = output.toString().trim();
            //    artifactVersion = marketingVersionNumber + "." + build.getNumber();
            //    listener.getLogger().println("CFBundleShortVersionString is " + marketingVersionNumber + " so new CFBundleVersion will be " + artifactVersion);
            //}

            returnCode = launcher.launch().envs(envs).cmds(getDescriptor().agvtoolPath(), "new-version", "-all", versionNumber ).stdout(listener).pwd(projectRoot).join();
            if(returnCode>0) {
                listener.fatalError("Could not set the CFBundleVersion to " + versionNumber);
            }
//        } else {
//            listener.getLogger().println("Fetching marketing version number (CFBundleShortVersionString)");
//            ByteArrayOutputStream output = new ByteArrayOutputStream();
//            returnCode = launcher.launch().envs(envs).cmds("agvtool", "vers", "-terse").stdout(output).pwd(projectRoot).join();
//
//            // only use this version number if we found it
//            if(returnCode==0)
//                artifactVersion = output.toString().trim();
        }
       
        if( false == StringUtils.isEmpty(overrideMarketingNumber) ) {
            listener.getLogger().println("Updating marketing version to " + overrideMarketingNumber);
           
            returnCode = launcher.launch().envs(envs).cmds(getDescriptor().agvtoolPath(), "new-marketing-version", overrideMarketingNumber ).stdout(listener).pwd(projectRoot).join();
            if(returnCode>0) {
                listener.fatalError("Could not set marketing version to " + overrideMarketingNumber);
            }
        }

        // Clean build directories
        if(cleanBeforeBuild) {
            listener.getLogger().println("Cleaning build directory (" + projectRoot.child("build") + ")");
            buildDirectory.deleteRecursive();
        }
       
        // remove test-reports and *.ipa
        listener.getLogger().println("Cleaning up test-reports");
        projectRoot.child("test-reports").deleteRecursive();

        // Build
        StringBuilder xcodeReport = new StringBuilder("Going to invoke xcodebuild: ");
        XCodeBuildOutputParser reportGenerator = new XCodeBuildOutputParser(projectRoot, listener);
        List<String> commandLine = Lists.newArrayList(getDescriptor().xcodebuildPath());
        if(StringUtils.isEmpty(target)) {
            commandLine.add("-alltargets");
            xcodeReport.append("target: ALL");
        } else {
            commandLine.add("-target");
            commandLine.add(target);
            xcodeReport.append("target: ").append(target);
        }
       
        if(!StringUtils.isEmpty(sdk)) {
            commandLine.add("-sdk");
            commandLine.add(sdk);
            xcodeReport.append(", sdk: ").append(sdk);
        } else {
            xcodeReport.append(", sdk: DEFAULT");
        }

        if(!StringUtils.isEmpty(xcodeProjectFile)) {
            commandLine.add("-project");
            commandLine.add(xcodeProjectFile);
            xcodeReport.append(", project: ").append(xcodeProjectFile);
        } else {
            xcodeReport.append(", project: DEFAULT");
        }

        commandLine.add("-configuration");
        commandLine.add(configuration);
        xcodeReport.append(", configuration: ").append(configuration);

//        if (cleanBeforeBuild) {
//            commandLine.add("clean");
//            xcodeReport.append(", clean: YES");
//        } else {
//            xcodeReport.append(", clean: NO");
//        }
        commandLine.add("build");
       
        listener.getLogger().println(xcodeReport.toString());
        returnCode = launcher.launch().envs(envs).cmds(commandLine).stdout(reportGenerator.getOutputStream()).pwd(projectRoot).join();
        if(reportGenerator.getExitCode()!=0) return false;
        if(returnCode>0) return false;


        // Package IPA
        if(buildIpa) {
            listener.getLogger().println("Cleaning up previously generate .ipa files");
            for(FilePath path : buildDirectory.list("*.ipa")) {
                path.delete();
            }

            listener.getLogger().println("Packaging IPA");
            List<FilePath> apps = buildDirectory.list(new AppFileFilter());

            for(FilePath app : apps) {
                String baseName = app.getBaseName() + "-" + configuration + "-" + build.getProject().getName() + "-" + versionNumber;
                FilePath ipaLocation = buildDirectory.child(baseName + ".ipa");

                FilePath payload = buildDirectory.child("Payload");
                payload.deleteRecursive();
                payload.mkdirs();
          

                listener.getLogger().println("Packaging " + app.getBaseName() + ".app => " + ipaLocation);
                List<String> packageCommandLine = new ArrayList<String>();
                packageCommandLine.add("/usr/bin/xcrun");
                packageCommandLine.add("-sdk");

                if(!StringUtils.isEmpty(sdk)) {
                    packageCommandLine.add(sdk);
                } else {
                    packageCommandLine.add("iphoneos");
                }
                packageCommandLine.addAll(Lists.newArrayList("PackageApplication", "-v", app.toString(), "-o", ipaLocation.toString()));
                if(!StringUtils.isEmpty(embeddedProfileFile)) {
                    packageCommandLine.add("--embed");
                    packageCommandLine.add(embeddedProfileFile);
                }
               
                returnCode = launcher.launch().envs(envs).stdout(listener).pwd(projectRoot).cmds(packageCommandLine).join();
                if(returnCode>0) {
                    listener.getLogger().println("Failed to build " + ipaLocation.getName());
                    continue;
                }

                // also zip up the symbols, if present
                returnCode = launcher.launch().envs(envs).stdout(listener).pwd(buildDirectory).cmds("zip", "-r", "-T", "-y", baseName + "-dSYM.zip", app.getBaseName() + ".app.dSYM").join();
                if(returnCode>0) {
                    listener.getLogger().println("Failed to zip *.dSYM into " + baseName + "-dSYM.zip");
                    continue;
                }

                //listener.getLogger().println("Copying to " + app.getBaseName() + ".ipa");
                //ipaLocation.copyTo(buildDirectory.child(app.getBaseName() + ".ipa"));


                payload.deleteRecursive();
            }
        }

        return true;
    }

    @Override
    public DescriptorImpl getDescriptor() {
        return (DescriptorImpl)super.getDescriptor();
    }

    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
        private String xcodebuildPath = "/usr/bin/xcodebuild";
        private String agvtoolPath = "/usr/bin/agvtool";

        public FormValidation doCheckConfiguration(@QueryParameter String value) throws IOException, ServletException {
            if (StringUtils.isEmpty(value)) {
                return FormValidation.error("Please specify a configuration");
            } else {
                // TODO: scan project file for specified configuration
            }
            return FormValidation.ok();
        }

        public FormValidation doCheckXcodebuildPath(@QueryParameter String value) throws IOException, ServletException {
            if (StringUtils.isEmpty(value)) {
                return FormValidation.error("Please specify the path to the xcodebuild executable (usually /usr/bin/xcodebuild)");
            } else {
                // TODO: check that the file exists
            }
            return FormValidation.ok();
        }

        public FormValidation doCheckAgvtoolPath(@QueryParameter String value) throws IOException, ServletException {
            if(StringUtils.isEmpty(value))
                return FormValidation.error("Please specify the path to the agvtool executable (usually /usr/bin/agvtool)");
            else {
                // TODO: check that the file exists
            }
            return FormValidation.ok();
        }

        public boolean isApplicable(Class<? extends AbstractProject> aClass) {
            // indicates that this builder can be used with all kinds of project types
            return true;
        }

        public String getDisplayName() {
            return "XCode";
        }

        @Override
        public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
            // To persist global configuration information,
            // set that to properties and call save().
//            updateBuildNumber = formData.getBoolean("updateBuildNumber");
//            buildIpa = formData.getBoolean("buildIpa");
//            cleanBeforeBuild = formData.getBoolean("cleanBeforeBuild");
//            configuration = formData.getString("configuration");
            xcodebuildPath = formData.getString("xcodebuildPath");
            agvtoolPath = formData.getString("agvtoolPath");
            // ^Can also use req.bindJSON(this, formData);
            //  (easier when there are many fields; need set* methods for this, like setUseFrench)
            save();
            return super.configure(req, formData);
        }

        public String agvtoolPath() {
            return agvtoolPath;
        }

        public String xcodebuildPath() {
            return xcodebuildPath;
        }

    }
}
TOP

Related Classes of au.com.rayh.XCodeBuilder$DescriptorImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.