Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
rayh committed Sep 24, 2010
1 parent 80f099e commit 5a90a5d
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 0 deletions.
20 changes: 20 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
This is a very simple plugin for invoking xcode builds from Hudson CI (http://hudson-ci.org). There are two builders provided, one for agvtool and one for xcodebuild. This plugin was build against Hudson 1.343.

agvtool Builder
---

This builder allows the dynamic updating of the CFBundleVersion with the current build number and a pre-defined major/minor marketing version to produce a version number of the form MAJOR.MINOR.BUILD_NUMBER (e.g. 1.0.456)

XCode Builder
---

This invokes the actual build. Currently it will invoke -alltargets. The configuration (Debug, Release, etc) can be specified in the per-project config along with whether to perform a clean before the build phase.


GOTCHAS
-------

- Obviously, the build machine has to be an OSX machine with XCode developer tools installed
- Certificates, Identities and Provisions must be installed on the build machine separately
- When code-signing, a prompt may appear on the build machine asking whether to allow keychain access. This will block the build until it is dismissed. Just select 'Always Allow' the first time and it shouldnt need to ask again.
- Make sure you define the CFBundleShortVersionString in your Info.plist. This will be used as the marketing version and will be prepended to the build number when using agvtool
37 changes: 37 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.jvnet.hudson.plugins</groupId>
<artifactId>plugin</artifactId>
<version>1.343</version><!-- which version of Hudson is this plugin built against? -->
<relativePath>../pom.xml</relativePath>
</parent>

<developers>
<developer>
<id>rayhilton</id>
<name>Ray Yamamoto Hilton</name>
<email>[email protected]</email>
</developer>
</developers>

<groupId>hudson.plugins.xcode</groupId>
<artifactId>xcode-builder</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>hpi</packaging>

<!-- get every artifact through maven.glassfish.org, which proxies all the artifacts that we need -->
<repositories>
<repository>
<id>m.g.o-public</id>
<url>http://maven.glassfish.org/content/groups/public/</url>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>m.g.o-public</id>
<url>http://maven.glassfish.org/content/groups/public/</url>
</pluginRepository>
</pluginRepositories>
</project>
151 changes: 151 additions & 0 deletions src/main/java/au/com/rayh/XCodeBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package au.com.rayh;
import com.google.common.collect.Lists;
import hudson.EnvVars;
import hudson.Launcher;
import hudson.Extension;
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.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 = "Release";

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

public String getConfiguration() {
return configuration;
}

public Boolean getBuildIpa() {
return buildIpa;
}

public Boolean getCleanBeforeBuild() {
return cleanBeforeBuild;
}

public Boolean getUpdateBuildNumber() {
return updateBuildNumber;
}

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

// XCode Version
launcher.launch().envs(envs).cmds(getDescriptor().xcodebuildPath(), "-version").stdout(listener.getLogger()).pwd(build.getProject().getWorkspace()).join();

// Set build number
if(updateBuildNumber) {
launcher.launch().envs(envs).cmds(getDescriptor().agvtoolPath(), "new-version", "-all", "`agvtool mvers -terse1`." + build.getNumber() ).stdout(listener.getLogger()).pwd(build.getProject().getWorkspace()).join();
}

// Build
List<String> commandLine = Lists.newArrayList(getDescriptor().xcodebuildPath(), "-alltargets", "-configuration", configuration);
if (cleanBeforeBuild) {
commandLine.add("clean");
}
commandLine.add("build");
int returnCode = launcher.launch().envs(envs).cmds(commandLine).stdout(listener.getLogger()).pwd(build.getProject().getWorkspace()).join();

return returnCode==0;
}

@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;
}

}
}

28 changes: 28 additions & 0 deletions src/main/resources/au/com/rayh/XCodeBuilder/config.jelly
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<!--
This jelly script is used for per-project configuration.
See global.jelly for a general discussion about jelly script.
-->

<f:entry title="Configuration" field="configuration"
help="/plugin/xcodebuild-hudson-plugin/help-configuration.html">
<f:textbox name="xcode.configuration" value="${instance.configuration}" />
</f:entry>

<f:entry title="Update version with build number?" field="updateBuildNumber"
help="/plugin/xcodebuild-hudson-plugin/help-updateBuildNumber.html">
<f:checkbox name="xcode.updateBuildNumber" checked="${instance.updateBuildNumber}" />
</f:entry>

<f:entry title="Clean before build?" field="cleanBeforeBuild"
help="/plugin/xcodebuild-hudson-plugin/help-cleanBeforeBuild.html">
<f:checkbox name="xcode.cleanBeforeBuild" checked="${instance.cleanBeforeBuild}" />
</f:entry>

<!-- <f:entry title="Build IPA?" field="buildIpa"
help="/plugin/xcodebuild-hudson-plugin/help-buildIpa.html">
<f:checkbox name="xcode.buildIpa" checkbox="${instance.buildIpa}" />
</f:entry>-->
</j:jelly>
12 changes: 12 additions & 0 deletions src/main/resources/au/com/rayh/XCodeBuilder/global.jelly
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:section title="XCode Builder">

<f:entry title="xcodebuild executable path" field="xcodebuildPath">
<f:textbox name="xcode.xcodebuildPath" checked="${descriptor.xcodebuildPath()}" />
</f:entry>

<f:entry title="agvtool executable path" field="agvtoolPath">
<f:textbox name="xcode.agvtoolPath" checked="${descriptor.agvtoolPath()}" />
</f:entry>
</f:section>
</j:jelly>
8 changes: 8 additions & 0 deletions src/main/resources/index.jelly
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!--
This view is used to render the plugin list page.
Since we don't really have anything dynamic here, let's just use static HTML.
-->
<div>
This plugin provides builders to build xcode projects, invoke agvtool and package .ipa files
</div>
4 changes: 4 additions & 0 deletions src/main/webapp/help-buildIpa.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div>
<p>
Checking this option will create a .ipa for each .app found in the build directory. An .ipa is basically a zipped up .app. This is quite handy for distributing ad-hoc builds to testers as they can just double-click the .ipa and it will import into iTunes.</p>
</div>
5 changes: 5 additions & 0 deletions src/main/webapp/help-cleanBeforeBuild.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
<p>
This invokes xcodebuild clean before build. This will force the rebuilding of ALL project files, including compressing of pngs, etc for every build and is quite a lot slower on large projects.
</p>
</div>
5 changes: 5 additions & 0 deletions src/main/webapp/help-configuration.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
<p>
This is the name of the configuration as defined in the XCode project. By default there are Debug and Release configurations.
</p>
</div>
5 changes: 5 additions & 0 deletions src/main/webapp/help-updateBuildNumber.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div>
<p>
Make sure CFBundleShortVersionString is set in your Info.plist to an appropriate marketing version number (e.g. 1.0). Checking this box will update the current project version in the project file as well as the CFBundleVersion in the Info.plist to the marketing version suffixed with the build number (e.g. 1.0.3234)
</p>
</div>

0 comments on commit 5a90a5d

Please sign in to comment.