diff --git a/build.gradle b/build.gradle index afccbb92..8889fd2a 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ allprojects { apply plugin: 'java-gradle-plugin' apply plugin: 'groovy' - def versionNumber = "0.2.8-SNAPSHOT" + def versionNumber = "0.16.0.a603076-SNAPSHOT" if (project.hasProperty("versionNumber")) { versionNumber = project.versionNumber @@ -55,6 +55,27 @@ allprojects { substitute module('ch.qos.logback:logback-classic') with module('org.slf4j:slf4j-api:1.7.+') } } + + // Write the plugin's classpath to a file to share with the tests + task createClasspathManifest { + def outputDir = file("$buildDir/$name") + + inputs.files sourceSets.main.runtimeClasspath + outputs.dir outputDir + + doLast { + outputDir.mkdirs() + file("$outputDir/plugin-classpath.txt").text = project.files(project.rootProject.subprojects + .collect { it.sourceSets.main.runtimeClasspath } + .toList()) + .files + .join("\n") + } + } + + dependencies { + testRuntime files(createClasspathManifest) + } } diff --git a/libxcode/src/main/groovy/org/openbakery/CommandRunner.groovy b/libxcode/src/main/groovy/org/openbakery/CommandRunner.groovy index d845bacc..b52aeeff 100644 --- a/libxcode/src/main/groovy/org/openbakery/CommandRunner.groovy +++ b/libxcode/src/main/groovy/org/openbakery/CommandRunner.groovy @@ -38,7 +38,6 @@ class CommandRunner { } - void setOutputFile(File outputFile) { if (outputFile.exists()) { @@ -172,20 +171,35 @@ class CommandRunner { } String runWithResult(String... commandList) { - return runWithResult(Arrays.asList(commandList)); + return runWithResult(Arrays.asList(commandList)) + } + + String runWithResult(Map environmentValues, + String... commandList) { + return runWithResult(defaultBaseDirectory, + commandList.toList(), + environmentValues, + null) } String runWithResult(List commandList) { return runWithResult(defaultBaseDirectory, commandList) } - String runWithResult(String directory, List commandList) { - return runWithResult(directory, commandList, null, null) + String runWithResult(String directory, + List commandList) { + return runWithResult(directory, + commandList, + null, + null) } - String runWithResult(String directory, List commandList, Map environment, OutputAppender outputAppender) { + String runWithResult(String directory, + List commandList, + Map environment, + OutputAppender outputAppender) { commandOutputBuffer = new ArrayList<>(); - run(directory, commandList, environment, outputAppender); + run(directory, commandList, environment, outputAppender) String result = commandOutputBuffer.join("\n") commandOutputBuffer = null; return result diff --git a/libxcode/src/main/groovy/org/openbakery/codesign/ProvisioningProfileReader.groovy b/libxcode/src/main/groovy/org/openbakery/codesign/ProvisioningProfileReader.groovy index ead64e05..121b6cc8 100644 --- a/libxcode/src/main/groovy/org/openbakery/codesign/ProvisioningProfileReader.groovy +++ b/libxcode/src/main/groovy/org/openbakery/codesign/ProvisioningProfileReader.groovy @@ -69,7 +69,10 @@ class ProvisioningProfileReader { checkExpired() } - static File getProvisionFileForIdentifier(String bundleIdentifier, List mobileProvisionFiles, CommandRunner commandRunner, PlistHelper plistHelper) { + static File getProvisionFileForIdentifier(String bundleIdentifier, + List mobileProvisionFiles, + CommandRunner commandRunner, + PlistHelper plistHelper) { def provisionFileMap = [:] for (File mobileProvisionFile : mobileProvisionFiles) { @@ -99,8 +102,8 @@ class ProvisioningProfileReader { } } - logger.info("No provisioning profile found for bundle identifier {}", bundleIdentifier) - logger.info("Available bundle identifier are {}" + provisionFileMap.keySet()) + logger.warn("No provisioning profile found for bundle identifier {}", bundleIdentifier) + logger.warn("Available bundle identifier are {}" + provisionFileMap.keySet()) return null } @@ -142,7 +145,7 @@ class ProvisioningProfileReader { Date expireDate = config.getProperty("ExpirationDate") if (expireDate.before(new Date())) { DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.getDefault()) - throw new IllegalArgumentException("The Provisioning Profile has expired on " + formatter.format(expireDate) ) + throw new IllegalArgumentException("The Provisioning Profile has expired on " + formatter.format(expireDate)) } } @@ -159,6 +162,14 @@ class ProvisioningProfileReader { return config.getString("TeamIdentifier") } + String getTeamName() { + return config.getString("TeamName") + } + + String getName() { + return config.getString("Name") + } + File getPlistFromProvisioningProfile() { if (provisioningPlist == null) { // unpack provisioning profile to plain plist @@ -170,12 +181,12 @@ class ProvisioningProfileReader { try { commandRunner.run(["security", - "cms", - "-D", - "-i", - provisioningProfile.getCanonicalPath(), - "-o", - provisioningPlist.absolutePath + "cms", + "-D", + "-i", + provisioningProfile.getCanonicalPath(), + "-o", + provisioningPlist.absolutePath ]) } catch (CommandRunnerException ex) { if (!provisioningPlist.exists()) { @@ -204,15 +215,16 @@ class ProvisioningProfileReader { } /* xcent is the archive entitlements */ + void extractEntitlements(File entitlementFile, String bundleIdentifier, List keychainAccessGroups, Configuration configuration) { logger.info("extractEntitlements for " + bundleIdentifier) File plistFromProvisioningProfile = getPlistFromProvisioningProfile() String entitlements = commandRunner.runWithResult([ - "/usr/libexec/PlistBuddy", - "-x", - plistFromProvisioningProfile.absolutePath, - "-c", - "Print Entitlements"]) + "/usr/libexec/PlistBuddy", + "-x", + plistFromProvisioningProfile.absolutePath, + "-c", + "Print Entitlements"]) if (StringUtils.isEmpty(entitlements)) { logger.debug("No entitlements found in {}", plistFromProvisioningProfile) @@ -228,7 +240,7 @@ class ProvisioningProfileReader { String bundleIdentifierPrefix = "" if (applicationIdentifier != null) { String[] tokens = applicationIdentifier.split("\\.") - for (int i=1; i if (value instanceof String) { value = this.replaceVariables(value) } else if (value instanceof List) { - value = this.replaceValuesInList((List)value) + value = this.replaceValuesInList((List) value) } @@ -308,7 +319,7 @@ class ProvisioningProfileReader { def result = [] for (Object item : list) { if (item instanceof String) { - result << replaceVariables((String)item) + result << replaceVariables((String) item) } else { result << item } @@ -330,7 +341,7 @@ class ProvisioningProfileReader { } Configuration entitlements = new ConfigurationFromPlist(entitlementFile) - SetreplaceKeys = configuration.getReplaceEntitlementsKeys() + Set replaceKeys = configuration.getReplaceEntitlementsKeys() for (String key in configuration.getKeys()) { Object value = configuration.get(key) //plistHelper.getValueFromPlist(xcent, key) @@ -366,7 +377,7 @@ class ProvisioningProfileReader { } else { if (currentValue.toString().endsWith('*')) { - plistHelper.setValueForPlist(entitlementFile, value, prefix + "." + bundleIdentifier) + plistHelper.setValueForPlist(entitlementFile, value, prefix + "." + bundleIdentifier) } } } diff --git a/libxcode/src/main/groovy/org/openbakery/codesign/Security.groovy b/libxcode/src/main/groovy/org/openbakery/codesign/Security.groovy index 53034462..401ef292 100644 --- a/libxcode/src/main/groovy/org/openbakery/codesign/Security.groovy +++ b/libxcode/src/main/groovy/org/openbakery/codesign/Security.groovy @@ -1,6 +1,5 @@ package org.openbakery.codesign -import org.apache.commons.io.FileUtils import org.apache.commons.io.FilenameUtils import org.apache.commons.lang.StringUtils import org.openbakery.CommandRunner @@ -9,7 +8,6 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import java.security.cert.CertificateException -import java.text.ParseException class Security { private static Logger logger = LoggerFactory.getLogger(Security.class) @@ -60,6 +58,7 @@ class Security { for (File keychain in keychainList) { commandList.add(keychain.absolutePath) } + commandRunner.run(commandList) } diff --git a/libxcode/src/main/groovy/org/openbakery/util/PathHelper.groovy b/libxcode/src/main/groovy/org/openbakery/util/PathHelper.groovy index 53d5f3e9..b7a96265 100644 --- a/libxcode/src/main/groovy/org/openbakery/util/PathHelper.groovy +++ b/libxcode/src/main/groovy/org/openbakery/util/PathHelper.groovy @@ -1,6 +1,7 @@ package org.openbakery.util import groovy.transform.CompileStatic +import org.gradle.api.Project import org.openbakery.xcode.Type @CompileStatic @@ -9,6 +10,10 @@ class PathHelper { public static final String APPLE_TV_SIMULATOR = "appletvsimulator" public static final String IPHONE_SIMULATOR = "iphonesimulator" public static final String IPHONE_OS = "iphoneos" + public static final String FOLDER_ARCHIVE = "archive" + public static final String FOLDER_PACKAGE = "package" + public static final String GENERATED_XCARCHIVE_FILE_NAME = "archive.xcconfig" + public static final String EXTENSION_XCARCHIVE = ".xcarchive" static File resolvePath(Type type, boolean simulator, @@ -70,4 +75,25 @@ class PathHelper { return new File(symRoot, "${configuration}-${destination}") } + + static File resolveArchiveFolder(Project project) { + return new File(project.getBuildDir(), FOLDER_ARCHIVE) + } + + static File resolveArchiveFile(Project project, + String scheme) { + return new File(resolveArchiveFolder(project), scheme + EXTENSION_XCARCHIVE) + } + + static File resolveArchivingLogFile(Project project) { + return new File(resolveArchiveFolder(project), "xcodebuild-archive-output.txt") + } + + static File resolveXcConfigFile(Project project) { + return new File(resolveArchiveFolder(project), GENERATED_XCARCHIVE_FILE_NAME) + } + + static File resolvePackageFolder(Project project) { + return new File(project.getBuildDir(), FOLDER_PACKAGE) + } } diff --git a/libxcode/src/main/groovy/org/openbakery/util/PlistHelper.groovy b/libxcode/src/main/groovy/org/openbakery/util/PlistHelper.groovy index 48a689e0..256e23d1 100644 --- a/libxcode/src/main/groovy/org/openbakery/util/PlistHelper.groovy +++ b/libxcode/src/main/groovy/org/openbakery/util/PlistHelper.groovy @@ -17,16 +17,15 @@ class PlistHelper { this.commandRunner = commandRunner } - /** - * Reads the value for the given key from the given plist - * - * @param plist - * @param key - * @param commandRunner The commandRunner to execute commands (This is espacially needed for Unit Tests) - * - * @return returns the value for the given key - */ + * Reads the value for the given key from the given plist + * + * @param plist + * @param key + * @param commandRunner The commandRunner to execute commands (This is espacially needed for Unit Tests) + * + * @return returns the value for the given key + */ def getValueFromPlist(File plist, String key) { try { @@ -75,6 +74,12 @@ class PlistHelper { commandForPlist(plist, "Add :" + key + " string " + value) } + void addDictForPlist(File plist, String key, Map map) { + commandForPlist(plist, "Add :" + key + " dict") + map.keySet() + .each { it -> addValueForPlist(plist, ":$key:$it", map.get(it)) } + } + void addValueForPlist(File plist, String key, Number value) { if (value instanceof Float || value instanceof Double || value instanceof BigDecimal) { commandForPlist(plist, "Add :" + key + " real " + value) diff --git a/libxcode/src/main/groovy/org/openbakery/xcode/DestinationResolver.groovy b/libxcode/src/main/groovy/org/openbakery/xcode/DestinationResolver.groovy index 870cc36f..be14a92f 100644 --- a/libxcode/src/main/groovy/org/openbakery/xcode/DestinationResolver.groovy +++ b/libxcode/src/main/groovy/org/openbakery/xcode/DestinationResolver.groovy @@ -36,10 +36,8 @@ class DestinationResolver { def runtime = simulatorControl.getMostRecentRuntime(parameters.type) - if (isSimulatorFor(parameters)) { // filter only on simulator builds - logger.debug("is a simulator build") if (parameters.configuredDestinations != null) { diff --git a/libxcode/src/main/groovy/org/openbakery/xcode/Version.groovy b/libxcode/src/main/groovy/org/openbakery/xcode/Version.groovy index de92bc57..0652acf1 100644 --- a/libxcode/src/main/groovy/org/openbakery/xcode/Version.groovy +++ b/libxcode/src/main/groovy/org/openbakery/xcode/Version.groovy @@ -1,6 +1,6 @@ package org.openbakery.xcode -class Version implements Comparable { +class Version implements Comparable, Serializable { public int major = -1 public int minor = -1 @@ -11,7 +11,7 @@ class Version implements Comparable { public Version() { } - public Version(String version) { + public Version(String version) { Scanner versionScanner = new Scanner(version); versionScanner.useDelimiter("\\."); @@ -30,8 +30,6 @@ class Version implements Comparable { } catch (InputMismatchException ex) { suffix = versionScanner.next() } - - } @Override @@ -71,13 +69,13 @@ class Version implements Comparable { builder.append(minor) } - if (this.maintenance>= 0) { + if (this.maintenance >= 0) { builder.append(".") builder.append(maintenance) } if (this.suffix != null) { - if (builder.length() > 0 ){ + if (builder.length() > 0) { builder.append(".") } builder.append(suffix) diff --git a/libxcode/src/main/groovy/org/openbakery/xcode/Xcode.groovy b/libxcode/src/main/groovy/org/openbakery/xcode/Xcode.groovy index 8bc50f27..89f80ad2 100644 --- a/libxcode/src/main/groovy/org/openbakery/xcode/Xcode.groovy +++ b/libxcode/src/main/groovy/org/openbakery/xcode/Xcode.groovy @@ -1,24 +1,38 @@ package org.openbakery.xcode +import groovy.transform.CompileStatic +import org.gradle.internal.impldep.com.google.common.annotations.VisibleForTesting import org.openbakery.CommandRunner import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.util.regex.Matcher +import java.util.regex.Pattern + +@CompileStatic class Xcode { - private static Logger logger = LoggerFactory.getLogger(Xcode.class) + private Version version = null + private String xcodePath + @VisibleForTesting + private CommandRunner commandRunner - CommandRunner commandRunner + public static final String ENV_DEVELOPER_DIR = "DEVELOPER_DIR" + public static final String XCODE_ACTION_XC_SELECT = "xcode-select" + public static final String XCODE_CONTENT_DEVELOPER = "Contents/Developer" + public static final String XCODE_CONTENT_XC_RUN = "/$XCODE_CONTENT_DEVELOPER/usr/bin/xcrun" + public static + final String XCODE_CONTENT_XCODE_BUILD = "$XCODE_CONTENT_DEVELOPER/usr/bin/xcodebuild" - String xcodePath - Version version = null + private final Logger logger = LoggerFactory.getLogger(Xcode.class) + private static final Pattern VERSION_PATTERN = ~/Xcode\s([^\s]*)\nBuild\sversion\s([^\s]*)/ - public Xcode(CommandRunner commandRunner) { + Xcode(CommandRunner commandRunner) { this(commandRunner, null) } - public Xcode(CommandRunner commandRunner, String version) { + Xcode(CommandRunner commandRunner, String version) { logger.debug("create xcode with version {}", version) this.commandRunner = commandRunner if (version != null) { @@ -26,40 +40,83 @@ class Xcode { } } - void setVersionFromString(String version) { - Version versionToCompare = new Version(version) - String installedXcodes = commandRunner.runWithResult("mdfind", "kMDItemCFBundleIdentifier=com.apple.dt.Xcode") - - - for (String xcode : installedXcodes.split("\n")) { - File xcodeBuildFile = new File(xcode, "Contents/Developer/usr/bin/xcodebuild"); - if (xcodeBuildFile.exists()) { - Version xcodeVersion = getXcodeVersion(xcodeBuildFile.absolutePath) - if (xcodeVersion.suffix != null && versionToCompare.suffix != null) { - if (xcodeVersion.suffix.equalsIgnoreCase(versionToCompare.suffix)) { - xcodePath = xcode - this.version = xcodeVersion - return - } - } else if (xcodeVersion.toString().startsWith(versionToCompare.toString())) { - xcodePath = xcode - this.version = xcodeVersion - return - } - } + CommandRunner getCommandRunner() { + return commandRunner + } + + /** + * Provide the environments values to provide to the command line runner to select + * a Xcode version without using `xcode-select -s` who requires `sudo`. + * + * @param version : The required Xcode version + * @return A map of environment variables to pass to the command runner + */ + Map getXcodeSelectEnvValue(String version) { + setVersionFromString(version) + File file = new File(xcodePath, XCODE_CONTENT_DEVELOPER) + HashMap result = new HashMap() + if (file.exists()) { + result.put(ENV_DEVELOPER_DIR, file.absolutePath) } - throw new IllegalStateException("No Xcode found with build number " + version); + println file.absolutePath + return result + } + + void setVersionFromString(String version) throws IllegalArgumentException { + Optional result = resolveXcodeInstallOfVersion(version) + + if (result.isPresent()) { + selectXcode(result.get()) + } else { + throw new IllegalStateException("No Xcode found with build number " + version) + } + } + + Optional resolveXcodeInstallOfVersion(String version) { + if (version == null) + return Optional.empty() + final Version requiredVersion = new Version(version) + + return Optional.ofNullable(resolveInstalledXcodeVersionsList() + .split("\n") + .iterator() + .collect { new File(it as File, XCODE_CONTENT_XCODE_BUILD) } + .findAll { it.exists() } + .find { + Version candidate = getXcodeVersion(it.absolutePath) + + boolean versionStartWith = candidate.toString() + .startsWith(requiredVersion.toString()) + + boolean versionHasSuffix = (candidate.suffix != null + && requiredVersion.suffix != null + && candidate.suffix.equalsIgnoreCase(requiredVersion.suffix)) + + return versionHasSuffix || versionStartWith + }) } - Version getXcodeVersion(String xcodebuildCommand) { - String xcodeVersion = commandRunner.runWithResult(xcodebuildCommand, "-version"); + void selectXcode(File file) { + String absolutePath = file.absolutePath + Version xcodeVersion = getXcodeVersion(absolutePath) + xcodePath = new File(absolutePath - XCODE_CONTENT_XCODE_BUILD) + this.version = xcodeVersion + } - def VERSION_PATTERN = ~/Xcode\s([^\s]*)\nBuild\sversion\s([^\s]*)/ - def matcher = VERSION_PATTERN.matcher(xcodeVersion) + String resolveInstalledXcodeVersionsList() { + return commandRunner.runWithResult("mdfind", + "kMDItemCFBundleIdentifier=com.apple.dt.Xcode") + } + + Version getXcodeVersion(String xcodeBuildCommand) { + String xcodeVersion = commandRunner.runWithResult(xcodeBuildCommand, + "-version") + + Matcher matcher = VERSION_PATTERN.matcher(xcodeVersion) if (matcher.matches()) { - Version version = new Version(matcher[0][1]) - version.suffix = matcher[0][2] + Version version = new Version(matcher.group(1)) + version.suffix = matcher.group(2) return version } return null @@ -74,37 +131,38 @@ class Xcode { String getPath() { if (xcodePath == null) { - String result = commandRunner.runWithResult("xcode-select", "-p") - xcodePath = result - "/Contents/Developer" + String result = commandRunner.runWithResult(XCODE_ACTION_XC_SELECT + , "-p") + xcodePath = result - "/$XCODE_CONTENT_DEVELOPER" } return xcodePath } - String getXcodebuild() { if (xcodePath != null) { - return xcodePath + "/Contents/Developer/usr/bin/xcodebuild" + return new File(xcodePath, XCODE_CONTENT_XCODE_BUILD).absolutePath } return "xcodebuild" } String getAltool() { - return getPath() + "/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool" + return getPath() + "/Contents/Applications/Application Loader" + + ".app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool" } String getXcrun() { - return getPath() + "/Contents/Developer/usr/bin/xcrun" + return getPath() + XCODE_CONTENT_XC_RUN } String getSimctl() { - return getPath() + "/Contents/Developer/usr/bin/simctl" + return getPath() + "/$XCODE_CONTENT_DEVELOPER/usr/bin/simctl" } @Override - public String toString() { + String toString() { return "Xcode{" + - "xcodePath='" + xcodePath + '\'' + - ", version=" + version + - '}'; + "xcodePath='" + xcodePath + '\'' + + ", version=" + version + + '}' } } diff --git a/libxcode/src/main/groovy/org/openbakery/xcode/Xcodebuild.groovy b/libxcode/src/main/groovy/org/openbakery/xcode/Xcodebuild.groovy index 86fdd945..b84bae48 100644 --- a/libxcode/src/main/groovy/org/openbakery/xcode/Xcodebuild.groovy +++ b/libxcode/src/main/groovy/org/openbakery/xcode/Xcodebuild.groovy @@ -3,10 +3,11 @@ package org.openbakery.xcode import org.openbakery.CommandRunner import org.openbakery.output.OutputAppender +import javax.annotation.Nullable + class Xcodebuild { CommandRunner commandRunner - HashMap buildSettings = null File projectDirectory @@ -15,7 +16,14 @@ class Xcodebuild { XcodebuildParameters parameters List destinations - + public static final String EXECUTABLE = "xcodebuild" + public static final String ACTION_ARCHIVE = "archive" + public static final String ACTION_EXPORT_ARCHIVE = "-exportArchive" + public static final String ARGUMENT_SCHEME = "-scheme" + public static final String ARGUMENT_ARCHIVE_PATH = "-archivePath" + public static final String ARGUMENT_EXPORT_PATH = "-exportPath" + public static final String ARGUMENT_EXPORT_OPTIONS_PLIST = "-exportOptionsPlist" + public static final String ARGUMENT_XCCONFIG = "-xcconfig" public Xcodebuild(File projectDirectory, CommandRunner commandRunner, Xcode xcode, XcodebuildParameters parameters, List destinations) { this.projectDirectory = projectDirectory @@ -39,6 +47,45 @@ class Xcodebuild { } } + static void packageIpa(CommandRunner commandRunner, + File archivePath, + File exportPath, + File exportOptionsPlist) { + + assert archivePath != null && archivePath.exists() + assert exportPath != null && exportPath.exists() + assert exportOptionsPlist != null && exportOptionsPlist.exists() + + commandRunner.run(EXECUTABLE, + ACTION_EXPORT_ARCHIVE, + ARGUMENT_ARCHIVE_PATH, archivePath.absolutePath, + ARGUMENT_EXPORT_PATH, exportPath.absolutePath, + ARGUMENT_EXPORT_OPTIONS_PLIST, exportOptionsPlist.absolutePath) + } + + static void archive(CommandRunner commandRunner, + String scheme, + File outputPath, + File xcConfig, + @Nullable File xcodeApp) { + assert scheme != null + assert xcConfig.exists() && !xcConfig.isDirectory() + + HashMap envMap = new HashMap<>() + + if (xcodeApp != null) { + envMap.put(Xcode.ENV_DEVELOPER_DIR, xcodeApp.absolutePath) + } + + List args = [EXECUTABLE, + ACTION_ARCHIVE, + ARGUMENT_SCHEME, scheme, + ARGUMENT_ARCHIVE_PATH, outputPath.absolutePath, + ARGUMENT_XCCONFIG, xcConfig.absolutePath] + + commandRunner.run(args, envMap) + } + def execute(OutputAppender outputAppender, Map environment) { validateParameters(outputAppender, environment) def commandList = [] @@ -283,51 +330,51 @@ class Xcodebuild { String getToolchainDirectory() { - String buildSetting = getBuildSetting("TOOLCHAIN_DIR") - if (buildSetting != null) { - return buildSetting - } - return "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain" + String buildSetting = getBuildSetting("TOOLCHAIN_DIR") + if (buildSetting != null) { + return buildSetting } + return "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain" + } String getPlatformDirectory() { return getBuildSetting("PLATFORM_DIR") } - String loadBuildSettings() { - def commandList = [xcode.xcodebuild, "clean", "-showBuildSettings"] - - if (parameters.scheme != null && parameters.workspace != null) { - commandList.add("-scheme"); - commandList.add(parameters.scheme) - commandList.add("-workspace") - commandList.add(parameters.workspace) - } + String loadBuildSettings() { + def commandList = [xcode.xcodebuild, "clean", "-showBuildSettings"] - return commandRunner.runWithResult(this.projectDirectory.absolutePath, commandList) + if (parameters.scheme != null && parameters.workspace != null) { + commandList.add("-scheme"); + commandList.add(parameters.scheme) + commandList.add("-workspace") + commandList.add(parameters.workspace) } - private String getBuildSetting(String key) { - if (buildSettings == null) { - buildSettings = new HashMap<>() - String[] buildSettingsData = loadBuildSettings().split("\n") - for (line in buildSettingsData) { - int index = line.indexOf("=") - if (index > 0) { - String settingsKey = line.substring(0, index).trim() - String settingsValue = line.substring(index + 1, line.length()).trim() - buildSettings.put(settingsKey, settingsValue) - } + return commandRunner.runWithResult(this.projectDirectory.absolutePath, commandList) + } + + private String getBuildSetting(String key) { + if (buildSettings == null) { + buildSettings = new HashMap<>() + String[] buildSettingsData = loadBuildSettings().split("\n") + for (line in buildSettingsData) { + int index = line.indexOf("=") + if (index > 0) { + String settingsKey = line.substring(0, index).trim() + String settingsValue = line.substring(index + 1, line.length()).trim() + buildSettings.put(settingsKey, settingsValue) } } - return buildSettings.get(key) } + return buildSettings.get(key) + } @Override public String toString() { return "Xcodebuild{" + - "xcodePath='" + xcodePath + '\'' + - parameters + - '}' + "xcodePath='" + xcodePath + '\'' + + parameters + + '}' } } diff --git a/libxcode/src/test/groovy/org/openbakery/xcode/XcodeSpecification.groovy b/libxcode/src/test/groovy/org/openbakery/xcode/XcodeSpecification.groovy index 7ab51de2..41e4342d 100644 --- a/libxcode/src/test/groovy/org/openbakery/xcode/XcodeSpecification.groovy +++ b/libxcode/src/test/groovy/org/openbakery/xcode/XcodeSpecification.groovy @@ -2,224 +2,249 @@ package org.openbakery.xcode import org.apache.commons.io.FileUtils import org.openbakery.CommandRunner -import org.openbakery.xcode.Version -import org.openbakery.xcode.Xcode import spock.lang.Specification +import spock.lang.Unroll class XcodeSpecification extends Specification { - Xcode xcode + Xcode xcode - CommandRunner commandRunner = Mock(CommandRunner) + CommandRunner commandRunner = Mock(CommandRunner) - File xcode7_1_1 - File xcode6_1 - File xcode6_0 - File xcode5_1 + static File xcode7_1_1 = new File(File.createTempDir(), "Xcode7.1.1.app") + static File xcode6_1 = new File(File.createTempDir(), "Xcode6-1.app") + static File xcode6_0 = new File(File.createTempDir(), "Xcode6.app") + static File xcode5_1 = new File(File.createTempDir(), "Xcode5.app") - def setup() { + def setup() { + xcode = Spy(Xcode, constructorArgs: [commandRunner]) + + new File(xcode7_1_1, "Contents/Developer/usr/bin").mkdirs() + new File(xcode6_1, "Contents/Developer/usr/bin").mkdirs() + new File(xcode6_0, "Contents/Developer/usr/bin").mkdirs() + new File(xcode5_1, "Contents/Developer/usr/bin").mkdirs() - xcode = new Xcode(commandRunner) + new File(xcode7_1_1, "Contents/Developer/usr/bin/xcodebuild").createNewFile() + new File(xcode6_1, "Contents/Developer/usr/bin/xcodebuild").createNewFile() + new File(xcode6_0, "Contents/Developer/usr/bin/xcodebuild").createNewFile() + new File(xcode5_1, "Contents/Developer/usr/bin/xcodebuild").createNewFile() + } + + def cleanup() { + FileUtils.deleteDirectory(xcode7_1_1) + FileUtils.deleteDirectory(xcode6_1) + FileUtils.deleteDirectory(xcode6_0) + FileUtils.deleteDirectory(xcode5_1) + } - xcode7_1_1 = new File(System.getProperty("java.io.tmpdir"), "Xcode7.1.1.app") - xcode6_1 = new File(System.getProperty("java.io.tmpdir"), "Xcode6-1.app") - xcode6_0 = new File(System.getProperty("java.io.tmpdir"), "Xcode6.app") - xcode5_1 = new File(System.getProperty("java.io.tmpdir"), "Xcode5.app") - new File(xcode7_1_1, "Contents/Developer/usr/bin").mkdirs() - new File(xcode6_1, "Contents/Developer/usr/bin").mkdirs() - new File(xcode6_0, "Contents/Developer/usr/bin").mkdirs() - new File(xcode5_1, "Contents/Developer/usr/bin").mkdirs() + def "test default xcode path"() { + given: + useDefaultXcode() - new File(xcode7_1_1, "Contents/Developer/usr/bin/xcodebuild").createNewFile() - new File(xcode6_1, "Contents/Developer/usr/bin/xcodebuild").createNewFile() - new File(xcode6_0, "Contents/Developer/usr/bin/xcodebuild").createNewFile() - new File(xcode5_1, "Contents/Developer/usr/bin/xcodebuild").createNewFile() + expect: + xcode.getPath().equals("/Applications/Xcode.app") + } + def useXcode(String version) { + mockInstalledXcodeVersions() - } + xcode = new Xcode(commandRunner, version) + } - def cleanup() { - FileUtils.deleteDirectory(xcode7_1_1) - FileUtils.deleteDirectory(xcode6_1) - FileUtils.deleteDirectory(xcode6_0) - FileUtils.deleteDirectory(xcode5_1) - } + def mockInstalledXcodeVersions() { + commandRunner.runWithResult(xcode5_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> ("Xcode 5.1.1\nBuild version 5B1008") + commandRunner.runWithResult(xcode6_0.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> ("Xcode 6.0\nBuild version 6A000") + commandRunner.runWithResult(xcode6_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> ("Xcode 6.4\nBuild version 6E35b") + commandRunner.runWithResult(xcode7_1_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> ("Xcode 7.1.1\nBuild version 7B1005") + commandRunner.runWithResult("mdfind", "kMDItemCFBundleIdentifier=com.apple.dt.Xcode") >> xcode5_1.absolutePath + "\n" + xcode6_0.absolutePath + "\n" + xcode6_1.absolutePath + "\n" + xcode7_1_1.absolutePath + } - def "test default xcode path"() { - given: - useDefaultXcode() - - expect: - xcode.getPath().equals("/Applications/Xcode.app") - - } - - - def useXcode(String version) { - commandRunner.runWithResult(xcode5_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> ("Xcode 5.1.1\nBuild version 5B1008") - commandRunner.runWithResult(xcode6_0.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> ("Xcode 6.0\nBuild version 6A000") - commandRunner.runWithResult(xcode6_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> ("Xcode 6.4\nBuild version 6E35b") - commandRunner.runWithResult(xcode7_1_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> ("Xcode 7.1.1\nBuild version 7B1005") - commandRunner.runWithResult("mdfind", "kMDItemCFBundleIdentifier=com.apple.dt.Xcode") >> xcode5_1.absolutePath + "\n" + xcode6_0.absolutePath + "\n" + xcode6_1.absolutePath + "\n" + xcode7_1_1.absolutePath - - xcode = new Xcode(commandRunner, version) - } - - - def useDefaultXcode() { - commandRunner.runWithResult("xcode-select", "-p") >> ("/Applications/Xcode.app/Contents/Developer") - } - - def "xcodebuild of Xcode 5 is used"() { - given: - useXcode("5B1008") - - expect: - xcode.getXcodebuild().endsWith("Xcode5.app/Contents/Developer/usr/bin/xcodebuild") - } - - def "xcodebuild of Xcode 5 simple version number"() { - given: - useXcode("5.1") - - expect: - xcode.getXcodebuild().endsWith("Xcode5.app/Contents/Developer/usr/bin/xcodebuild") - } - - - def "xcode Version Simple 1"() { - - given: - useXcode("5.1.1") - - expect: - xcode.getXcodebuild().endsWith("Xcode5.app/Contents/Developer/usr/bin/xcodebuild") - } - - def "xcodeVersion Simple not found"() { - when: - useXcode("5.1.2") - - then: - thrown(IllegalStateException) - } - - - def "xcodeVersion select last"() { - commandRunner.runWithResult("mdfind", "kMDItemCFBundleIdentifier=com.apple.dt.Xcode") >> xcode6_1.absolutePath + "\n" + xcode6_0.absolutePath + "\n" + xcode5_1.absolutePath - commandRunner.runWithResult(xcode6_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> "Xcode 6.0\nBuild version 6A000" - commandRunner.runWithResult(xcode6_0.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> "Xcode 6.0\nBuild version 6A000" - commandRunner.runWithResult(xcode5_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> "Xcode 5.1.1\nBuild version 5B1008" - - when: - xcode = new Xcode(commandRunner, '5B1008') - - then: - xcode.getXcodebuild().endsWith("Xcode5.app/Contents/Developer/usr/bin/xcodebuild") - } - - - def "xcodeVersion select not found"() { - useXcode("5B1008") - - when: - xcode = new Xcode(commandRunner, '5B1009') - - then: - thrown(IllegalStateException) - } - - def "version is not null"() { - given: - commandRunner.runWithResult("xcodebuild", "-version") >> ("Xcode 7.3.1\nBuild version 7D1014") - - expect: - xcode.getVersion() != null - xcode.getVersion().major == 7 - xcode.getVersion().minor == 3 - xcode.getVersion().maintenance == 1 - } - - - def "altool default path"() { - given: - useDefaultXcode() - - expect: - xcode.getAltool() == '/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool' - } - - def "altool with xcode 7.1.1"() { - given: - useXcode("7.1") - - expect: - xcode.getAltool().contains('Xcode7.1.1.app') - xcode.getAltool().endsWith('Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool') - } - - def "xcrun default path"() { - given: - useDefaultXcode() - - expect: - xcode.getXcrun() == '/Applications/Xcode.app/Contents/Developer/usr/bin/xcrun' - } - - - def "xcrun with xcode 7.1.1"() { - given: - useXcode("7.1") - - expect: - xcode.getXcrun().endsWith('Xcode7.1.1.app/Contents/Developer/usr/bin/xcrun') - } - - - - def "set xcode version"() { - useXcode("7.1") - - - expect: - xcode.version instanceof Version - xcode.version.major == 7 - xcode.version.minor == 1 - xcode.version.maintenance == 1 - } - - def "set xcode version with xcode 6"() { - useXcode("6.4") - - expect: - xcode.version instanceof Version - xcode.version.major == 6 - xcode.version.minor == 4 - xcode.version.maintenance == -1 - } - - def "get xcode version"() { - given: - commandRunner.runWithResult("xcodebuild", "-version") >> ("Xcode 6.4\nBuild version 6E35b") - - when: - Version version = xcode.version - - then: - version instanceof Version - version.major == 6 - version.minor == 4 - version.maintenance == -1 - } - - - def "simctl default path"() { - given: - useDefaultXcode() - - expect: - xcode.getSimctl() == '/Applications/Xcode.app/Contents/Developer/usr/bin/simctl' - } + def useDefaultXcode() { + commandRunner.runWithResult("xcode-select", "-p") >> ("/Applications/Xcode.app/Contents/Developer") + } + @Unroll + def "xcodebuild of Xcode 5 is used"() { + given: + useXcode("5B1008") + expect: + xcode.getXcodebuild() + .endsWith("Xcode5.app/Contents/Developer/usr/bin/xcodebuild") + + where: + version | _ + "5B1008" | _ + "5.1" | _ + "5.1.1" | _ + } + + def "xcodeVersion Simple not found"() { + when: + useXcode("5.1.2") + + then: + thrown(IllegalStateException) + } + + def "xcodeVersion select last"() { + commandRunner.runWithResult("mdfind", "kMDItemCFBundleIdentifier=com.apple.dt.Xcode") >> xcode6_1.absolutePath + "\n" + xcode6_0.absolutePath + "\n" + xcode5_1.absolutePath + commandRunner.runWithResult(xcode6_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> "Xcode 6.0\nBuild version 6A000" + commandRunner.runWithResult(xcode6_0.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> "Xcode 6.0\nBuild version 6A000" + commandRunner.runWithResult(xcode5_1.absolutePath + "/Contents/Developer/usr/bin/xcodebuild", "-version") >> "Xcode 5.1.1\nBuild version 5B1008" + + when: + xcode = new Xcode(commandRunner, '5B1008') + + then: + xcode.getXcodebuild().endsWith("Xcode5.app/Contents/Developer/usr/bin/xcodebuild") + } + + + def "xcodeVersion select not found"() { + useXcode("5B1008") + + when: + xcode = new Xcode(commandRunner, '5B1009') + + then: + thrown(IllegalStateException) + } + + def "version is not null"() { + given: + commandRunner.runWithResult("xcodebuild", "-version") >> ("Xcode 7.3.1\nBuild version 7D1014") + + expect: + xcode.getVersion() != null + xcode.getVersion().major == 7 + xcode.getVersion().minor == 3 + xcode.getVersion().maintenance == 1 + } + + + def "altool default path"() { + given: + useDefaultXcode() + + expect: + xcode.getAltool() == '/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool' + } + + def "altool with xcode 7.1.1"() { + given: + useXcode("7.1") + + expect: + xcode.getAltool().contains('Xcode7.1.1.app') + xcode.getAltool().endsWith('Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Support/altool') + } + + def "xcrun default path"() { + given: + useDefaultXcode() + + expect: + xcode.getXcrun() == '/Applications/Xcode.app/Contents/Developer/usr/bin/xcrun' + } + + + def "xcrun with xcode 7.1.1"() { + given: + useXcode("7.1") + + expect: + xcode.getXcrun().endsWith('Xcode7.1.1.app/Contents/Developer/usr/bin/xcrun') + } + + def "set xcode version"() { + setup: + useXcode(version) + + expect: + xcode.version instanceof Version + xcode.version.major == major + xcode.version.minor == minor + xcode.version.maintenance == maintenance + + where: + version | major | minor | maintenance + "5.1.1" | 5 | 1 | 1 + "6.0" | 6 | 0 | -1 + "6.4" | 6 | 4 | -1 + "7.1.1" | 7 | 1 | 1 + } + + def "get xcode version"() { + given: + commandRunner.runWithResult("xcodebuild", "-version") >> ("Xcode 6.4\nBuild version 6E35b") + + when: + Version version = xcode.version + + then: + version instanceof Version + version.major == 6 + version.minor == 4 + version.maintenance == -1 + } + + + def "simctl default path"() { + given: + useDefaultXcode() + + expect: + xcode.getSimctl() == '/Applications/Xcode.app/Contents/Developer/usr/bin/simctl' + } + + def "Should be able to resolve a Xcode version by string without exception when valid"() { + when: + mockInstalledXcodeVersions() + xcode.setVersionFromString(version) + + then: + 1 * xcode.selectXcode(new File(file, Xcode.XCODE_CONTENT_XCODE_BUILD)) + xcode.version.toString() == (version + "." + buildVersion) + noExceptionThrown() + + where: + version | buildVersion | file + "5.1.1" | "5B1008" | xcode5_1 + "6.0" | "6A000" | xcode6_0 + "7.1.1" | "7B1005" | xcode7_1_1 + } + + def "Should raise an exception when resolving a invalid Xcode instance by version string"() { + when: + mockInstalledXcodeVersions() + xcode.setVersionFromString(version) + + then: + thrown(exception) + 0 * xcode.selectXcode(_) + + where: + version | exception + "5.1.3" | IllegalStateException + "10.0" | IllegalStateException + "7.1.3" | IllegalStateException + null | IllegalStateException + } + + def "Should return a map of environment values containing the developer dir key for valid xcode version"() { + when: + mockInstalledXcodeVersions() + Map envValues = xcode.getXcodeSelectEnvValue(version) + + then: + noExceptionThrown() + envValues.get(Xcode.ENV_DEVELOPER_DIR) == new File(file, Xcode.XCODE_CONTENT_DEVELOPER).absolutePath + + where: + version | file + "5.1.1" | xcode5_1 + "6.0" | xcode6_0 + "7.1.1" | xcode7_1_1 + } } diff --git a/plugin/build.gradle b/plugin/build.gradle index fc1ff613..83cac330 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -45,6 +45,7 @@ dependencies { compile 'org.pegdown:pegdown:1.5.+' compile 'org.openbakery.coverage:CoverageReport:0.9.4' deployerJars 'org.apache.maven.wagon:wagon-ssh:2.10' + compile 'de.undercouch:gradle-download-task:3.4.3' } jar { @@ -59,6 +60,38 @@ task sourcesJar(type: Jar) { classifier = 'sources' } +sourceSets { + test { + groovy { + srcDir file('src/test/groovy') + } + resources { + srcDir file('src/test/Resource') + } + } + + functionalTest { + groovy { + srcDir file('src/functionalTest/groovy') + } + resources { + srcDir file('src/functionalTest/resources') + } + compileClasspath += sourceSets.main.output + configurations.testRuntime + runtimeClasspath += output + compileClasspath + } +} + +task functionalTest(type: Test) { + testClassesDirs = sourceSets.functionalTest.output.classesDirs + classpath = sourceSets.functionalTest.runtimeClasspath +} + +check.dependsOn functionalTest + +gradlePlugin { + testSourceSets sourceSets.functionalTest +} uploadArchives { repositories { diff --git a/plugin/src/functionalTest/groovy/org/openbakery/KeychainCreateTaskFunctionalTest.groovy b/plugin/src/functionalTest/groovy/org/openbakery/KeychainCreateTaskFunctionalTest.groovy new file mode 100644 index 00000000..c93801a1 --- /dev/null +++ b/plugin/src/functionalTest/groovy/org/openbakery/KeychainCreateTaskFunctionalTest.groovy @@ -0,0 +1,160 @@ +package org.openbakery + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import org.openbakery.signing.KeychainCreateTask +import spock.lang.Shared +import spock.lang.Specification + +import java.nio.file.Paths + +class KeychainCreateTaskFunctionalTest extends Specification { + + @Rule + final TemporaryFolder testProjectDir = new TemporaryFolder() + + List pluginClasspath + + @Shared + File certificate + + File buildFile + + def setup() { + def pluginClasspathResource = getClass().classLoader.findResource("plugin-classpath.txt") + if (pluginClasspathResource == null) { + throw new IllegalStateException("Did not find plugin classpath resource, run `testClasses` build task.") + } + + pluginClasspath = pluginClasspathResource.readLines().collect { new File(it) } + + certificate = findResource("fake_distribution.p12") + assert certificate.exists() + + buildFile = testProjectDir.newFile('build.gradle') + buildFile << """ + plugins { + id 'org.openbakery.xcode-plugin' + } + """ + } + + def "The task list should contain the task"() { + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('tasks') + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.output.contains(KeychainCreateTask.TASK_NAME + + " - " + + KeychainCreateTask.TASK_DESCRIPTION) + } + + def "The task should be skipped if invalid configuration"() { + when: + BuildResult result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(KeychainCreateTask.TASK_NAME) + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.output.contains("No signing certificate defined, will skip the keychain creation") + result.output.contains("No signing certificate password defined, will skip the keychain creation") + result.task(":" + KeychainCreateTask.TASK_NAME).outcome == TaskOutcome.SKIPPED + } + + def "The task should be skipped if not certificate password is provided"() { + setup: + buildFile << """ + xcodebuild { + signing { + certificate = project.provisioningFile1("$certificate") + } + } + """ + + when: + BuildResult result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(KeychainCreateTask.TASK_NAME) + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.task(":" + KeychainCreateTask.TASK_NAME).outcome == TaskOutcome.SKIPPED + } + + def "The task should be executed if configuration is valid"() { + setup: + buildFile << """ + xcodebuild { + signing { + certificate = project.provisioningFile1("$certificate") + certificatePassword = "p4ssword" + } + } + """ + + when: + BuildResult result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(KeychainCreateTask.TASK_NAME) + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.task(":" + KeychainCreateTask.TASK_NAME).outcome == TaskOutcome.SUCCESS + } + + def "The task should automatically delete the temporary keychain file"() { + setup: + buildFile << """ + xcodebuild { + signing { + certificate = project.provisioningFile1("$certificate") + certificatePassword = "p4ssword" + } + } + """ + + when: + BuildResult result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(KeychainCreateTask.TASK_NAME) + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.task(":" + KeychainCreateTask.TASK_NAME).outcome == TaskOutcome.SUCCESS + + and: "The temporary certificate provisioningFile1 should be deleted automatically" + new File(testProjectDir.root, "build/codesign") + .listFiles() + .toList() + .findAll {it.name.endsWith(".p12")} + .empty + + and: "The temporary keychain provisioningFile1 should be deleted automatically" + new File(testProjectDir.root, "build/codesign") + .listFiles() + .toList() + .findAll {it.name.endsWith(".keychain")} + .empty + } + + private File findResource(String name) { + ClassLoader classLoader = getClass().getClassLoader() + return (File) Optional.ofNullable(classLoader.getResource(name)) + .map { URL url -> url.toURI() } + .map { URI uri -> Paths.get(uri).toFile() } + .filter { File file -> file.exists() } + .orElseThrow { new Exception("Resource $name cannot be found") } + } +} diff --git a/plugin/src/functionalTest/groovy/org/openbakery/PackageTaskIosAndTvOSTest.groovy b/plugin/src/functionalTest/groovy/org/openbakery/PackageTaskIosAndTvOSTest.groovy new file mode 100644 index 00000000..83c7d69b --- /dev/null +++ b/plugin/src/functionalTest/groovy/org/openbakery/PackageTaskIosAndTvOSTest.groovy @@ -0,0 +1,49 @@ +package org.openbakery + +import org.gradle.testkit.runner.GradleRunner +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import org.openbakery.packaging.PackageTaskIosAndTvOS +import spock.lang.Specification + +class PackageTaskIosAndTvOSTest extends Specification { + + @Rule + final TemporaryFolder testProjectDir = new TemporaryFolder() + + List pluginClasspath + + File buildFile + + def setup() { + buildFile = testProjectDir.newFile('build.gradle') + + def pluginClasspathResource = getClass().classLoader.findResource("plugin-classpath.txt") + if (pluginClasspathResource == null) { + throw new IllegalStateException("Did not find plugin classpath resource, run `testClasses` build task.") + } + + pluginClasspath = pluginClasspathResource.readLines().collect { new File(it) } + } + + def "The task list should contain the task"() { + given: + buildFile << """ + plugins { + id 'org.openbakery.xcode-plugin' + } + """ + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('tasks') + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.output.contains(PackageTaskIosAndTvOS.NAME + + " - " + + PackageTaskIosAndTvOS.DESCRIPTION) + } +} diff --git a/plugin/src/functionalTest/groovy/org/openbakery/PrepareXcodeArchivingFunctionalTest.groovy b/plugin/src/functionalTest/groovy/org/openbakery/PrepareXcodeArchivingFunctionalTest.groovy new file mode 100644 index 00000000..f1606a6d --- /dev/null +++ b/plugin/src/functionalTest/groovy/org/openbakery/PrepareXcodeArchivingFunctionalTest.groovy @@ -0,0 +1,193 @@ +package org.openbakery + +import org.apache.commons.io.FileUtils +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import org.openbakery.util.PathHelper +import spock.lang.Specification + +import java.nio.file.Paths + +class PrepareXcodeArchivingFunctionalTest extends Specification { + + @Rule + final TemporaryFolder testProjectDir = new TemporaryFolder() + + List pluginClasspath + + File buildFile + + + File provisioningFileWildCard + + def setup() { + pluginClasspath = findResource("plugin-classpath.txt") + .readLines() + .collect { new File(it) } + + FileUtils.copyDirectory(findResource("TestProject"), testProjectDir.getRoot()) + + provisioningFileWildCard = findResource("test1.mobileprovision") + } + + def setupBuildFile() { + buildFile = testProjectDir.newFile('build.gradle') + buildFile << """ + plugins { + id 'org.openbakery.xcode-plugin' + } + + xcodebuild { + target = 'TestProject' + scheme = "TestScheme" + signing { + mobileProvisionURI = "${provisioningFileWildCard.toURI().toString()}" + } + } + + """ + } + + def "The task list should contain the task"() { + given: + setupBuildFile() + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('tasks') + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.output.contains(PrepareXcodeArchivingTask.NAME + + " - " + + PrepareXcodeArchivingTask.DESCRIPTION) + } + + def "The task should complete without error and generate the xcconfig file"() { + setup: + setupBuildFile() + + when: + buildFile << """ + xcodebuild { + infoplist { + bundleIdentifier = "org.openbakery.test.ExampleWidget" + } + } + """ + + final File certificate = findResource("fake_distribution.p12") + assert certificate.exists() + buildFile << """ + xcodebuild { + infoplist { + bundleIdentifier = "org.openbakery.test.ExampleWidget" + } + + signing { + certificateURI = "${certificate.toURI().toString()}" + certificatePassword = "p4ssword" + } + } + """ + + BuildResult result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(PrepareXcodeArchivingTask.NAME) + .withPluginClasspath(pluginClasspath) + .build() + + then: "The task should complete without error" + + result.task(":" + PrepareXcodeArchivingTask.NAME) + .outcome == TaskOutcome.SUCCESS + + and: "The archive xcconfig provisioningFile1 should be properly generated and populated from configured values" + + File outputFile = new File(testProjectDir.root, "build/" + + PathHelper.FOLDER_ARCHIVE + + "/" + PathHelper.GENERATED_XCARCHIVE_FILE_NAME) + + outputFile.exists() + + String text = outputFile.text + text.contains("PRODUCT_BUNDLE_IDENTIFIER = org.openbakery.test.ExampleWidget") + text.contains("iPhone Distribution: Test Company Name (12345ABCDE)") + text.contains("PROVISIONING_PROFILE = XXXXFFFF-AAAA-BBBB-CCCC-DDDDEEEEFFFF") + text.contains("PROVISIONING_PROFILE_SPECIFIER = ad hoc") + text.contains("DEVELOPMENT_TEAM = XXXYYYZZZZ") + + and: "Should no contain any entitlements information" + !text.contains("CODE_SIGN_ENTITLEMENTS =") + } + + def "If present the entitlements file should be present into the xcconfig file"() { + setup: + setupBuildFile() + + when: + buildFile << """ + xcodebuild { + infoplist { + bundleIdentifier = "org.openbakery.test.ExampleWidget" + } + } + """ + + final File certificate = findResource("fake_distribution.p12") + assert certificate.exists() + + final File entitlementsFile = findResource("fake.entitlements") + assert entitlementsFile.exists() + + buildFile << """ + xcodebuild { + infoplist { + bundleIdentifier = "org.openbakery.test.ExampleWidget" + } + + signing { + certificateURI = "${certificate.toURI().toString()}" + certificatePassword = "p4ssword" + entitlementsFile = "${entitlementsFile.absolutePath}" + } + } + """ + + BuildResult result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(PrepareXcodeArchivingTask.NAME) + .withPluginClasspath(pluginClasspath) + .build() + + then: "The task should complete without error" + + result.task(":" + PrepareXcodeArchivingTask.NAME) + .outcome == TaskOutcome.SUCCESS + + and: "The archive xcconfig should contains the path to the entitlements provisioningFile1" + + File outputFile = new File(testProjectDir.root, "build/" + + PathHelper.FOLDER_ARCHIVE + + "/" + PathHelper.GENERATED_XCARCHIVE_FILE_NAME) + + outputFile.exists() + + String text = outputFile.text + text.contains("CODE_SIGN_ENTITLEMENTS = ${entitlementsFile.absolutePath}") + } + + private File findResource(String name) { + ClassLoader classLoader = getClass().getClassLoader() + return (File) Optional.ofNullable(classLoader.getResource(name)) + .map { URL url -> url.toURI() } + .map { URI uri -> Paths.get(uri).toFile() } + .filter { File file -> file.exists() } + .orElseThrow { new Exception("Resource $name cannot be found") } + } +} diff --git a/plugin/src/functionalTest/groovy/org/openbakery/signing/ProvisioningInstallTaskFunctionalTest.groovy b/plugin/src/functionalTest/groovy/org/openbakery/signing/ProvisioningInstallTaskFunctionalTest.groovy new file mode 100644 index 00000000..d20157ff --- /dev/null +++ b/plugin/src/functionalTest/groovy/org/openbakery/signing/ProvisioningInstallTaskFunctionalTest.groovy @@ -0,0 +1,156 @@ +package org.openbakery.signing + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Specification +import spock.lang.Unroll + +import java.nio.file.Paths + +class ProvisioningInstallTaskFunctionalTest extends Specification { + @Rule + final TemporaryFolder testProjectDir = new TemporaryFolder() + + List pluginClasspath + + File buildFile + File provisioningFile1 + + def setup() { + buildFile = testProjectDir.newFile('build.gradle') + + buildFile << """ + plugins { + id 'org.openbakery.xcode-plugin' + } + """ + + provisioningFile1 = findResource("test1.mobileprovision") + assert provisioningFile1.exists() + + def pluginClasspathResource = getClass().classLoader.findResource("plugin-classpath.txt") + if (pluginClasspathResource == null) { + throw new IllegalStateException("Did not find plugin classpath resource, run `testClasses` build task.") + } + + pluginClasspath = pluginClasspathResource.readLines().collect { new File(it) } + } + + def "The task list should contain the task"() { + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments('tasks') + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.output.contains(ProvisioningInstallTask.TASK_NAME + + " - " + + ProvisioningInstallTask.TASK_DESCRIPTION) + } + + def "If no provisioning is defined, then the task should be skipped"() { + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(ProvisioningInstallTask.TASK_NAME) + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.task(":" + ProvisioningInstallTask.TASK_NAME) + .outcome == TaskOutcome.SKIPPED + } + + def "If provisioning list is empty, then the task should be skipped"() { + setup: + buildFile << """ + xcodebuild { + signing { + mobileProvisionURI = [] + } + } + """ + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(ProvisioningInstallTask.TASK_NAME) + .withPluginClasspath(pluginClasspath) + .build() + + then: + result.task(":" + ProvisioningInstallTask.TASK_NAME) + .outcome == TaskOutcome.SKIPPED + } + + def "The provisioning can be configured via the mobileProvisionList list"() { + setup: + buildFile << """ + xcodebuild { + signing { + mobileProvisionList = ["${provisioningFile1.toURI().toString()}"] + } + } + """ + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(ProvisioningInstallTask.TASK_NAME) + .withPluginClasspath(pluginClasspath) + .withDebug(true) + .build() + + then: + result.task(":" + ProvisioningInstallTask.TASK_NAME) + .outcome == TaskOutcome.SUCCESS + } + + @Unroll + def "With gradle version : #gradleVersion If provisioning list is present, then the task should be skipped"() { + setup: + buildFile << """ + xcodebuild { + signing { + mobileProvisionURI = "${provisioningFile1.toURI().toString()}" + } + } + """ + + when: + def result = GradleRunner.create() + .withProjectDir(testProjectDir.root) + .withArguments(ProvisioningInstallTask.TASK_NAME) + .withPluginClasspath(pluginClasspath) + .withGradleVersion(gradleVersion) + .build() + + then: + result.task(":" + ProvisioningInstallTask.TASK_NAME) + .outcome == TaskOutcome.SUCCESS + + and: "The temporary provisioning provisioningFile1 should be deleted" + new File(testProjectDir.root, "build/provision") + .listFiles().size() == 0 + + where: + gradleVersion | _ + "4.4" | _ + "4.5" | _ + "4.6" | _ + "4.7" | _ + } + + private File findResource(String name) { + ClassLoader classLoader = getClass().getClassLoader() + return (File) Optional.ofNullable(classLoader.getResource(name)) + .map { URL url -> url.toURI() } + .map { URI uri -> Paths.get(uri).toFile() } + .filter { File file -> file.exists() } + .orElseThrow { new Exception("Resource $name cannot be found") } + } +} diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject.xcodeproj/project.pbxproj b/plugin/src/functionalTest/resources/TestProject/TestProject.xcodeproj/project.pbxproj new file mode 100644 index 00000000..ea040bd5 --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject.xcodeproj/project.pbxproj @@ -0,0 +1,337 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + BFD99BC5209C6A8800AF800E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD99BC4209C6A8800AF800E /* AppDelegate.swift */; }; + BFD99BC7209C6A8800AF800E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFD99BC6209C6A8800AF800E /* ViewController.swift */; }; + BFD99BCA209C6A8800AF800E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFD99BC8209C6A8800AF800E /* Main.storyboard */; }; + BFD99BCC209C6A8A00AF800E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFD99BCB209C6A8A00AF800E /* Assets.xcassets */; }; + BFD99BCF209C6A8A00AF800E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BFD99BCD209C6A8A00AF800E /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + BFD99BC1209C6A8800AF800E /* TestProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TestProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; + BFD99BC4209C6A8800AF800E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + BFD99BC6209C6A8800AF800E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + BFD99BC9209C6A8800AF800E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + BFD99BCB209C6A8A00AF800E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + BFD99BCE209C6A8A00AF800E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + BFD99BD0209C6A8A00AF800E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + BFD99BBE209C6A8800AF800E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + BFD99BB8209C6A8700AF800E = { + isa = PBXGroup; + children = ( + BFD99BC3209C6A8800AF800E /* TestProject */, + BFD99BC2209C6A8800AF800E /* Products */, + ); + sourceTree = ""; + }; + BFD99BC2209C6A8800AF800E /* Products */ = { + isa = PBXGroup; + children = ( + BFD99BC1209C6A8800AF800E /* TestProject.app */, + ); + name = Products; + sourceTree = ""; + }; + BFD99BC3209C6A8800AF800E /* TestProject */ = { + isa = PBXGroup; + children = ( + BFD99BC4209C6A8800AF800E /* AppDelegate.swift */, + BFD99BC6209C6A8800AF800E /* ViewController.swift */, + BFD99BC8209C6A8800AF800E /* Main.storyboard */, + BFD99BCB209C6A8A00AF800E /* Assets.xcassets */, + BFD99BCD209C6A8A00AF800E /* LaunchScreen.storyboard */, + BFD99BD0209C6A8A00AF800E /* Info.plist */, + ); + path = TestProject; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + BFD99BC0209C6A8800AF800E /* TestProject */ = { + isa = PBXNativeTarget; + buildConfigurationList = BFD99BD3209C6A8A00AF800E /* Build configuration list for PBXNativeTarget "TestProject" */; + buildPhases = ( + BFD99BBD209C6A8800AF800E /* Sources */, + BFD99BBE209C6A8800AF800E /* Frameworks */, + BFD99BBF209C6A8800AF800E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = TestProject; + productName = TestProject; + productReference = BFD99BC1209C6A8800AF800E /* TestProject.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BFD99BB9209C6A8700AF800E /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0930; + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = Massive; + TargetAttributes = { + BFD99BC0209C6A8800AF800E = { + CreatedOnToolsVersion = 9.3; + }; + }; + }; + buildConfigurationList = BFD99BBC209C6A8700AF800E /* Build configuration list for PBXProject "TestProject" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = BFD99BB8209C6A8700AF800E; + productRefGroup = BFD99BC2209C6A8800AF800E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + BFD99BC0209C6A8800AF800E /* TestProject */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + BFD99BBF209C6A8800AF800E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BFD99BCF209C6A8A00AF800E /* LaunchScreen.storyboard in Resources */, + BFD99BCC209C6A8A00AF800E /* Assets.xcassets in Resources */, + BFD99BCA209C6A8800AF800E /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + BFD99BBD209C6A8800AF800E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BFD99BC7209C6A8800AF800E /* ViewController.swift in Sources */, + BFD99BC5209C6A8800AF800E /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + BFD99BC8209C6A8800AF800E /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + BFD99BC9209C6A8800AF800E /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + BFD99BCD209C6A8A00AF800E /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + BFD99BCE209C6A8A00AF800E /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + BFD99BD1209C6A8A00AF800E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + BFD99BD2209C6A8A00AF800E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + BFD99BD4209C6A8A00AF800E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = TestProject/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = gradle.xcode.test.TestProject; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + BFD99BD5209C6A8A00AF800E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Automatic; + INFOPLIST_FILE = TestProject/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = gradle.xcode.test.TestProject; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + BFD99BBC209C6A8700AF800E /* Build configuration list for PBXProject "TestProject" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BFD99BD1209C6A8A00AF800E /* Debug */, + BFD99BD2209C6A8A00AF800E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BFD99BD3209C6A8A00AF800E /* Build configuration list for PBXNativeTarget "TestProject" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BFD99BD4209C6A8A00AF800E /* Debug */, + BFD99BD5209C6A8A00AF800E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BFD99BB9209C6A8700AF800E /* Project object */; +} diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/plugin/src/functionalTest/resources/TestProject/TestProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..2bf8bd93 --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/plugin/src/functionalTest/resources/TestProject/TestProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject/AppDelegate.swift b/plugin/src/functionalTest/resources/TestProject/TestProject/AppDelegate.swift new file mode 100644 index 00000000..0e25d993 --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject/AppDelegate.swift @@ -0,0 +1,46 @@ +// +// AppDelegate.swift +// TestProject +// +// Created by Johann Martinache on 04/05/2018. +// Copyright © 2018 Massive. All rights reserved. +// + +import UIKit + +@UIApplicationMain +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window: UIWindow? + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + func applicationWillResignActive(_ application: UIApplication) { + // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. + // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. + } + + func applicationDidEnterBackground(_ application: UIApplication) { + // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. + // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. + } + + func applicationWillEnterForeground(_ application: UIApplication) { + // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. + } + + func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + } + + func applicationWillTerminate(_ application: UIApplication) { + // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + } + + +} + diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject/Assets.xcassets/AppIcon.appiconset/Contents.json b/plugin/src/functionalTest/resources/TestProject/TestProject/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d8db8d65 --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject/Assets.xcassets/Contents.json b/plugin/src/functionalTest/resources/TestProject/TestProject/Assets.xcassets/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject/Base.lproj/LaunchScreen.storyboard b/plugin/src/functionalTest/resources/TestProject/TestProject/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f83f6fd5 --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject/Base.lproj/Main.storyboard b/plugin/src/functionalTest/resources/TestProject/TestProject/Base.lproj/Main.storyboard new file mode 100644 index 00000000..03c13c22 --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject/Base.lproj/Main.storyboard @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject/Info.plist b/plugin/src/functionalTest/resources/TestProject/TestProject/Info.plist new file mode 100644 index 00000000..16be3b68 --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/plugin/src/functionalTest/resources/TestProject/TestProject/ViewController.swift b/plugin/src/functionalTest/resources/TestProject/TestProject/ViewController.swift new file mode 100644 index 00000000..65114f2e --- /dev/null +++ b/plugin/src/functionalTest/resources/TestProject/TestProject/ViewController.swift @@ -0,0 +1,25 @@ +// +// ViewController.swift +// TestProject +// +// Created by Johann Martinache on 04/05/2018. +// Copyright © 2018 Massive. All rights reserved. +// + +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + // Do any additional setup after loading the view, typically from a nib. + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + +} + diff --git a/plugin/src/functionalTest/resources/fake.entitlements b/plugin/src/functionalTest/resources/fake.entitlements new file mode 100644 index 00000000..e69de29b diff --git a/plugin/src/functionalTest/resources/fake_distribution.p12 b/plugin/src/functionalTest/resources/fake_distribution.p12 new file mode 100644 index 00000000..208b05e0 Binary files /dev/null and b/plugin/src/functionalTest/resources/fake_distribution.p12 differ diff --git a/plugin/src/functionalTest/resources/test1.mobileprovision b/plugin/src/functionalTest/resources/test1.mobileprovision new file mode 100644 index 00000000..d8fded0c --- /dev/null +++ b/plugin/src/functionalTest/resources/test1.mobileprovision @@ -0,0 +1,46 @@ +// a Dummy Mobile Provistioning File to test the ProvisioningProfileIDReader + + + + ApplicationIdentifierPrefix + + AAAAAAAAAAA + + CreationDate + 2011-06-16T08:57:51Z + DeveloperCertificates + + + + + Entitlements + + application-identifier + AAAAAAAAAAA.org.openbakery.test.ExampleWidget + get-task-allow + + keychain-access-groups + + AAAAAAAAAAA.* + + + ExpirationDate + 2079-07-04T08:57:51Z + Name + ad hoc + ProvisionedDevices + + 1234 + + TimeToLive + 24855 + UUID + XXXXFFFF-AAAA-BBBB-CCCC-DDDDEEEEFFFF + Version + 1 + TeamIdentifier + + XXXYYYZZZZ + + + \ No newline at end of file diff --git a/plugin/src/main/groovy/org/openbakery/AbstractDistributeTask.groovy b/plugin/src/main/groovy/org/openbakery/AbstractDistributeTask.groovy index e2a36996..a0120cb1 100644 --- a/plugin/src/main/groovy/org/openbakery/AbstractDistributeTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/AbstractDistributeTask.groovy @@ -1,15 +1,14 @@ package org.openbakery import org.apache.commons.io.FileUtils -import org.openbakery.packaging.PackageTask +import org.openbakery.util.PathHelper import java.util.regex.Pattern - /** * User: rene * Date: 11/11/14 */ -class AbstractDistributeTask extends AbstractXcodeTask { +class AbstractDistributeTask extends AbstractXcodeBuildTask { private File archiveDirectory; @@ -89,7 +88,7 @@ class AbstractDistributeTask extends AbstractXcodeTask { } File getBundle(String extension) { - File packageDirectory = new File(project.getBuildDir(), PackageTask.PACKAGE_PATH) + File packageDirectory = PathHelper.resolvePackageFolder(project) if (!packageDirectory.exists()) { throw new IllegalStateException("package does not exist: " + packageDirectory) @@ -132,7 +131,7 @@ class AbstractDistributeTask extends AbstractXcodeTask { if (archiveDirectory != null) { return archiveDirectory; } - File archiveDirectory = new File(project.getBuildDir(), XcodeBuildArchiveTask.ARCHIVE_FOLDER) + File archiveDirectory = PathHelper.resolveArchiveFolder(project) if (!archiveDirectory.exists()) { throw new IllegalStateException("Archive does not exist: " + archiveDirectory) } diff --git a/plugin/src/main/groovy/org/openbakery/AbstractXcodeBuildTask.groovy b/plugin/src/main/groovy/org/openbakery/AbstractXcodeBuildTask.groovy index edb69c97..b50c49a7 100644 --- a/plugin/src/main/groovy/org/openbakery/AbstractXcodeBuildTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/AbstractXcodeBuildTask.groovy @@ -1,35 +1,40 @@ package org.openbakery +import groovy.transform.TypeChecked import org.gradle.api.logging.LogLevel import org.gradle.internal.logging.progress.ProgressLogger import org.gradle.internal.logging.progress.ProgressLoggerFactory import org.gradle.internal.logging.text.StyledTextOutput import org.gradle.internal.logging.text.StyledTextOutputFactory import org.gradle.util.ConfigureUtil -import org.openbakery.codesign.Security +import org.openbakery.codesign.ProvisioningProfileReader import org.openbakery.output.XcodeBuildOutputAppender import org.openbakery.xcode.Destination import org.openbakery.xcode.Devices import org.openbakery.xcode.Type import org.openbakery.xcode.XcodebuildParameters +import java.util.regex.Matcher +import java.util.regex.Pattern + /** * User: rene * Date: 15.07.13 * Time: 11:57 */ +@TypeChecked abstract class AbstractXcodeBuildTask extends AbstractXcodeTask { XcodebuildParameters parameters = new XcodebuildParameters() - private List destinationsCache + private static final Pattern PATTERN = ~/^\s{4}friendlyName:\s(?[^\n]+)/ + AbstractXcodeBuildTask() { super() } - void setTarget(String target) { parameters.target = target } @@ -90,10 +95,96 @@ abstract class AbstractXcodeBuildTask extends AbstractXcodeTask { XcodeBuildOutputAppender createXcodeBuildOutputAppender(String name) { - StyledTextOutput output = getServices().get(StyledTextOutputFactory.class).create(XcodeBuildTask.class, LogLevel.LIFECYCLE); - ProgressLoggerFactory progressLoggerFactory = getServices().get(ProgressLoggerFactory.class); - ProgressLogger progressLogger = progressLoggerFactory.newOperation(XcodeBuildTask.class).start(name, name); + StyledTextOutput output = getServices().get(StyledTextOutputFactory.class).create(XcodeBuildTask.class, LogLevel.LIFECYCLE) + ProgressLoggerFactory progressLoggerFactory = getServices().get(ProgressLoggerFactory.class) + ProgressLogger progressLogger = progressLoggerFactory.newOperation(XcodeBuildTask.class).start(name, name) return new XcodeBuildOutputAppender(progressLogger, output) } + XcodeBuildPluginExtension getXcodeExtension() { + return project.getExtensions() + .getByType(XcodeBuildPluginExtension.class) + } + + String getBundleIdentifier() { + File infoPlist = new File(project.projectDir, getXcodeExtension().infoPlist) + return plistHelper.getValueFromPlist(infoPlist, "CFBundleIdentifier") + } + + InfoPlistExtension getInfoPlistExtension() { + return project.getExtensions().getByType(InfoPlistExtension.class) + } + + File getProvisioningFile() { + List provisioningList = getProvisioningUriList() + .collect { it -> new File(new URI(it)) } + + println "provisioningList : " + provisioningList + + return Optional.ofNullable(ProvisioningProfileReader.getProvisionFileForIdentifier(bundleIdentifier, + provisioningList, + commandRunner, + plistHelper)).orElseThrow { + new IllegalArgumentException("Cannot resolve a valid provisioning " + + "profile for bundle identifier : " + bundleIdentifier) + } + } + + List getProvisioningUriList() { + return getXcodeExtension().signing.mobileProvisionList.get() + } + + String getSignatureFriendlyName() { + return Optional.ofNullable(getKeyContent() + .split(System.getProperty("line.separator")) + .find { PATTERN.matcher(it).matches() }) + .map { PATTERN.matcher(it) } + .filter { Matcher it -> it.matches() } + .map { Matcher it -> + return it.group("friendlyName") + } + .orElseThrow { + new IllegalArgumentException("Failed to resolve the code signing identity from the certificate ") + } + } + + private String getKeyContent() { + final String certificatePassword = getCertificatePassword() + File file = xcodeExtension.signing + .certificate + .get() + .asFile + + return commandRunner.runWithResult(["openssl", + "pkcs12", + "-nokeys", + "-in", + file.absolutePath, + "-passin", + "pass:" + certificatePassword]) + } + + private File getCertificateFile() { + return new File(URI.create(getCertificateString())) + } + + private String getCertificateString() throws IllegalArgumentException { +// return Optional.ofNullable(getXcodeExtension().signing.certificate) +// .filter { String it -> it != null && !it.isEmpty() } +// .orElseThrow { +// new IllegalArgumentException("The certificateURI value is null or empty : " + getXcodeExtension().signing.certificateURI) +// } + + return getXcodeExtension() + .signing + .certificate + } + + private String getCertificatePassword() { + return Optional.ofNullable(getXcodeExtension().signing.certificatePassword.getOrNull()) + .filter { it != null && !it.isEmpty() } + .orElseThrow { + new IllegalArgumentException("The signing certificate password is not defined") + } + } } diff --git a/plugin/src/main/groovy/org/openbakery/AbstractXcodeTask.groovy b/plugin/src/main/groovy/org/openbakery/AbstractXcodeTask.groovy index 33394036..8528ef94 100644 --- a/plugin/src/main/groovy/org/openbakery/AbstractXcodeTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/AbstractXcodeTask.groovy @@ -15,20 +15,17 @@ */ package org.openbakery - import org.apache.commons.io.FilenameUtils import org.apache.commons.lang.StringUtils import org.gradle.api.DefaultTask import org.openbakery.bundle.ApplicationBundle import org.openbakery.codesign.Security import org.openbakery.simulators.SimulatorControl +import org.openbakery.util.PlistHelper import org.openbakery.xcode.DestinationResolver -import org.openbakery.xcode.Version import org.openbakery.xcode.Xcode -import org.openbakery.util.PlistHelper import java.text.SimpleDateFormat - /** * * @author René Pirringer @@ -52,7 +49,6 @@ abstract class AbstractXcodeTask extends DefaultTask { security = new Security(commandRunner) } - /** * Copies a file to a new location * @@ -77,7 +73,7 @@ abstract class AbstractXcodeTask extends DefaultTask { ant.exec(failonerror: "true", - executable: 'ditto') { + executable: 'ditto') { arg(value: source.absolutePath) arg(value: destinationPath.absolutePath) } @@ -102,7 +98,7 @@ abstract class AbstractXcodeTask extends DefaultTask { } try { - ant.get(src: address, dest: toDirectory.getPath(), verbose:true) + ant.get(src: address, dest: toDirectory.getPath(), verbose: true) } catch (Exception ex) { logger.error("cannot download file from the given location: {}", address) throw ex @@ -112,23 +108,6 @@ abstract class AbstractXcodeTask extends DefaultTask { return destinationFile.absolutePath } - def getOSVersion() { - Version result = new Version() - String versionString = System.getProperty("os.version") - Scanner scanner = new Scanner(versionString).useDelimiter("\\.") - if (scanner.hasNext()) { - result.major = scanner.nextInt() - } - if (scanner.hasNext()) { - result.minor = scanner.nextInt() - } - if (scanner.hasNext()) { - result.maintenance = scanner.nextInt(); - } - return result - } - - def createZip(File fileToZip) { File zipFile = new File(fileToZip.parentFile, FilenameUtils.getBaseName(fileToZip.getName()) + ".zip") createZip(zipFile, zipFile.parentFile, fileToZip); @@ -151,7 +130,7 @@ abstract class AbstractXcodeTask extends DefaultTask { logger.debug("baseDirectory: {} ", baseDirectory) for (File file : filesToZip) { - logger.debug("create of: {}: {}", file, file.exists() ) + logger.debug("create of: {}: {}", file, file.exists()) } def arguments = [] @@ -166,8 +145,8 @@ abstract class AbstractXcodeTask extends DefaultTask { logger.debug("arguments: {}", arguments) ant.exec(failonerror: 'true', - executable: '/usr/bin/zip', - dir: baseDirectory) { + executable: '/usr/bin/zip', + dir: baseDirectory) { for (def argument : arguments) { arg(value: argument) @@ -189,7 +168,7 @@ abstract class AbstractXcodeTask extends DefaultTask { List getAppBundles(File appPath) { - ApplicationBundle applicationBundle = new ApplicationBundle(new File(appPath,project.xcodebuild.applicationBundle.name), project.xcodebuild.type, project.xcodebuild.simulator) + ApplicationBundle applicationBundle = new ApplicationBundle(new File(appPath, project.xcodebuild.applicationBundle.name), project.xcodebuild.type, project.xcodebuild.simulator) return applicationBundle.getBundles() } @@ -202,11 +181,18 @@ abstract class AbstractXcodeTask extends DefaultTask { Xcode getXcode() { if (xcode == null) { - xcode = new Xcode(commandRunner, project.xcodebuild.xcodeVersion) + xcode = new Xcode(commandRunner, getProjectXcodeVersion()) } return xcode } + String getProjectXcodeVersion() { + return project.extensions. + getByType(XcodeBuildPluginExtension). + version. + getOrNull() + } + DestinationResolver getDestinationResolver() { if (destinationResolver == null) { destinationResolver = new DestinationResolver(getSimulatorControl()) diff --git a/plugin/src/main/groovy/org/openbakery/InfoPlistExtension.groovy b/plugin/src/main/groovy/org/openbakery/InfoPlistExtension.groovy index 069ac692..17d237de 100644 --- a/plugin/src/main/groovy/org/openbakery/InfoPlistExtension.groovy +++ b/plugin/src/main/groovy/org/openbakery/InfoPlistExtension.groovy @@ -15,21 +15,28 @@ */ package org.openbakery +import org.gradle.api.Project +import org.gradle.api.provider.Property + class InfoPlistExtension { - def String bundleIdentifier = null - def String bundleIdentifierSuffix = null - def String bundleName = null - def String bundleDisplayName = null - def String bundleDisplayNameSuffix = null - def String version = null - def String versionSuffix = null - def String versionPrefix = null - def String shortVersionString = null - def String shortVersionStringSuffix = null - def String shortVersionStringPrefix = null - def List commands = null + String bundleIdentifier = null + String bundleIdentifierSuffix = null + String bundleName = null + String bundleDisplayName = null + String bundleDisplayNameSuffix = null + String version = null + String versionSuffix = null + String versionPrefix = null + String shortVersionString = null + String shortVersionStringSuffix = null + String shortVersionStringPrefix = null + List commands = null + final Property configurationBundleIdentifier + InfoPlistExtension(Project project) { + this.configurationBundleIdentifier = project.objects.property(String) + } void setCommands(Object commands) { if (commands instanceof List) { @@ -42,16 +49,16 @@ class InfoPlistExtension { boolean hasValuesToModify() { return bundleIdentifier != null || - bundleIdentifierSuffix != null || - bundleName != null || - bundleDisplayName != null || - bundleDisplayNameSuffix != null || - version != null || - versionSuffix != null || - versionPrefix != null || - shortVersionString != null || - shortVersionStringSuffix != null || - shortVersionStringPrefix != null || - commands != null; + bundleIdentifierSuffix != null || + bundleName != null || + bundleDisplayName != null || + bundleDisplayNameSuffix != null || + version != null || + versionSuffix != null || + versionPrefix != null || + shortVersionString != null || + shortVersionStringSuffix != null || + shortVersionStringPrefix != null || + commands != null; } -} \ No newline at end of file +} diff --git a/plugin/src/main/groovy/org/openbakery/InfoPlistModifyTask.groovy b/plugin/src/main/groovy/org/openbakery/InfoPlistModifyTask.groovy index 97116028..51b972a5 100644 --- a/plugin/src/main/groovy/org/openbakery/InfoPlistModifyTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/InfoPlistModifyTask.groovy @@ -15,24 +15,24 @@ */ package org.openbakery +import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskAction - class InfoPlistModifyTask extends AbstractDistributeTask { File infoPlist Boolean modfied = false + public final Provider configurationBundleIdentifier = project.objects.property(String) + + public static final String KeyBundleIdentifier = "CFBundleIdentifier" + public InfoPlistModifyTask() { dependsOn(XcodePlugin.XCODE_CONFIG_TASK_NAME) } - - - @TaskAction def prepare() { - if (!project.infoplist.hasValuesToModify()) { logger.debug("Nothing to modify") return; @@ -48,15 +48,7 @@ class InfoPlistModifyTask extends AbstractDistributeTask { logger.debug("Try to updating {}", infoPlist) - if (project.infoplist.bundleIdentifier != null) { - setValueForPlist("CFBundleIdentifier", project.infoplist.bundleIdentifier) - } - - // add suffix to bundleIdentifier - if (project.infoplist.bundleIdentifierSuffix != null) { - def bundleIdentifier = plistHelper.getValueFromPlist(infoPlist, "CFBundleIdentifier") - setValueForPlist("CFBundleIdentifier", bundleIdentifier + project.infoplist.bundleIdentifierSuffix) - } + modifyBundleIdentifier() // Modify bundle bundleName if (project.infoplist.bundleName != null) { @@ -80,7 +72,7 @@ class InfoPlistModifyTask extends AbstractDistributeTask { modifyShortVersion(infoPlist) - for(String command in project.infoplist.commands) { + for (String command in project.infoplist.commands) { setValueForPlist(command) } @@ -89,6 +81,8 @@ class InfoPlistModifyTask extends AbstractDistributeTask { } else { logger.debug("Nothing was modified!") } + + configurationBundleIdentifier.set(getBundleIdentifier()) } private void modifyVersion(File infoPlist) { @@ -143,22 +137,39 @@ class InfoPlistModifyTask extends AbstractDistributeTask { logger.debug("Modify CFBundleShortVersionString to {}", shortVersionString) setValueForPlist("CFBundleShortVersionString", shortVersionString) - } + private void modifyBundleIdentifier() { + // Resolve bundle identifier + XcodeBuildPluginExtension xcodeExtension = getXcodeExtension() + InfoPlistExtension extension = getInfoPlistExtension() + + if (extension.bundleIdentifier != null) { + setValueForPlist(KeyBundleIdentifier, extension.bundleIdentifier) + } else { + xcodeExtension.getBuildTargetConfiguration(xcodeExtension.scheme.getOrNull(), + xcodeExtension.configuration) + .map { it -> it.bundleIdentifier } + .ifPresent { it -> setValueForPlist(KeyBundleIdentifier, it) } + } + + // Add suffix to bundleIdentifier if defined + if (extension.bundleIdentifierSuffix != null) { + String bundleIdentifier = plistHelper.getValueFromPlist(infoPlist, KeyBundleIdentifier) + setValueForPlist(KeyBundleIdentifier, bundleIdentifier + extension.bundleIdentifierSuffix) + } + } void setValueForPlist(String key, String value) { modfied = true logger.lifecycle("Set {} to {}", key, value) plistHelper.setValueForPlist(infoPlist, key, value) - } - void setValueForPlist(String command) { modfied = true logger.lifecycle("Set {}", command) plistHelper.commandForPlist(infoPlist, command) } -} \ No newline at end of file +} diff --git a/plugin/src/main/groovy/org/openbakery/PrepareXcodeArchivingTask.groovy b/plugin/src/main/groovy/org/openbakery/PrepareXcodeArchivingTask.groovy new file mode 100644 index 00000000..57364b9c --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/PrepareXcodeArchivingTask.groovy @@ -0,0 +1,116 @@ +package org.openbakery + +import groovy.transform.CompileStatic +import org.gradle.api.DefaultTask +import org.gradle.api.Transformer +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* +import org.openbakery.codesign.ProvisioningProfileReader +import org.openbakery.signing.KeychainCreateTask +import org.openbakery.signing.ProvisioningInstallTask +import org.openbakery.util.PlistHelper + +@CompileStatic +class PrepareXcodeArchivingTask extends DefaultTask { + + @InputFile + @Optional + final Provider entitlementsFile = newInputFile() + + @OutputFile + final Provider outputFile = newOutputFile() + + final ListProperty registeredProvisioningFiles = project.objects.listProperty(File) + final Property commandRunnerProperty = project.objects.property(CommandRunner) + final Property provisioningForConfiguration = project.objects.property(File) + final Property plistHelperProperty = project.objects.property(PlistHelper) + final Property certificateFriendlyName = project.objects.property(String) + final Property configurationBundleIdentifier = project.objects.property(String) + final Property entitlementsFilePath = project.objects.property(String) + + @Internal + final Property provisioningReader = project.objects.property(ProvisioningProfileReader) + + public static final String DESCRIPTION = "Prepare the archive configuration file" + public static final String NAME = "prepareArchiving" + + static final String KEY_BUNDLE_IDENTIFIER = "PRODUCT_BUNDLE_IDENTIFIER" + static final String KEY_CODE_SIGN_IDENTITY = "CODE_SIGN_IDENTITY" + static final String KEY_CODE_SIGN_ENTITLEMENTS = "CODE_SIGN_ENTITLEMENTS" + static final String KEY_DEVELOPMENT_TEAM = "DEVELOPMENT_TEAM" + static final String KEY_PROVISIONING_PROFILE_ID = "PROVISIONING_PROFILE" + static final String KEY_PROVISIONING_PROFILE_SPEC = "PROVISIONING_PROFILE_SPECIFIER" + + PrepareXcodeArchivingTask() { + super() + + dependsOn(XcodePlugin.INFOPLIST_MODIFY_TASK_NAME) + dependsOn(XcodePlugin.XCODE_CONFIG_TASK_NAME) + dependsOn(KeychainCreateTask.TASK_NAME) + dependsOn(ProvisioningInstallTask.TASK_NAME) + + this.description = DESCRIPTION + + this.entitlementsFilePath.set(entitlementsFile.map(new Transformer() { + @Override + String transform(RegularFile regularFile) { + return regularFile.asFile.absolutePath + } + })) + + this.provisioningForConfiguration.set(configurationBundleIdentifier.map(new Transformer() { + @Override + File transform(String bundleIdentifier) { + return ProvisioningProfileReader.getProvisionFileForIdentifier(bundleIdentifier, + registeredProvisioningFiles.getOrNull() as List, + commandRunnerProperty.get(), + plistHelperProperty.get()) + } + })) + + this.provisioningReader.set(provisioningForConfiguration.map(new Transformer() { + @Override + ProvisioningProfileReader transform(File file) { + return new ProvisioningProfileReader(file, + commandRunnerProperty.get()) + } + })) + + this.onlyIf { + return certificateFriendlyName.present && + configurationBundleIdentifier.present && + outputFile.present && + provisioningForConfiguration.present + } + } + + @TaskAction + void generate() { + logger.info("Preparing archiving") + + outputFile.get().asFile.text = "" + + append(KEY_CODE_SIGN_IDENTITY, certificateFriendlyName.get()) + append(KEY_BUNDLE_IDENTIFIER, configurationBundleIdentifier.get()) + + if (provisioningReader.present) { + ProvisioningProfileReader reader = provisioningReader.get() + append(KEY_DEVELOPMENT_TEAM, reader.getTeamIdentifierPrefix()) + append(KEY_PROVISIONING_PROFILE_ID, reader.getUUID()) + append(KEY_PROVISIONING_PROFILE_SPEC, reader.getName()) + } + + if (entitlementsFilePath.present) { + append(KEY_CODE_SIGN_ENTITLEMENTS, entitlementsFilePath.get()) + } + } + + private void append(String key, String value) { + outputFile.get() + .asFile + .append(System.getProperty("line.separator") + key + " = " + value) + } +} diff --git a/plugin/src/main/groovy/org/openbakery/packaging/ReleaseNotesTask.groovy b/plugin/src/main/groovy/org/openbakery/ReleaseNotesTask.groovy similarity index 92% rename from plugin/src/main/groovy/org/openbakery/packaging/ReleaseNotesTask.groovy rename to plugin/src/main/groovy/org/openbakery/ReleaseNotesTask.groovy index e61856d7..7acb2854 100644 --- a/plugin/src/main/groovy/org/openbakery/packaging/ReleaseNotesTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/ReleaseNotesTask.groovy @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openbakery.packaging +package org.openbakery import org.apache.commons.io.FileUtils import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction - +import org.openbakery.util.PathHelper import org.pegdown.PegDownProcessor @@ -29,7 +29,7 @@ import org.pegdown.PegDownProcessor */ class ReleaseNotesTask extends DefaultTask { - File outputPath = new File(project.getBuildDir(), PackageTask.PACKAGE_PATH) + File outputPath = PathHelper.resolvePackageFolder(project) ReleaseNotesTask() { @@ -59,4 +59,4 @@ class ReleaseNotesTask extends DefaultTask { println "No changelog found!" } } -} \ No newline at end of file +} diff --git a/plugin/src/main/groovy/org/openbakery/XcodeBuildCleanTask.groovy b/plugin/src/main/groovy/org/openbakery/XcodeBuildCleanTask.groovy index 4facd4d9..d3c88ecf 100644 --- a/plugin/src/main/groovy/org/openbakery/XcodeBuildCleanTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/XcodeBuildCleanTask.groovy @@ -24,22 +24,19 @@ class XcodeBuildCleanTask extends DefaultTask { XcodeBuildCleanTask() { super() - dependsOn( - XcodePlugin.KEYCHAIN_CLEAN_TASK_NAME, - XcodePlugin.PROVISIONING_CLEAN_TASK_NAME, - XcodePlugin.HOCKEYAPP_CLEAN_TASK_NAME, - XcodePlugin.HOCKEYAPP_CLEAN_TASK_NAME, - XcodePlugin.DEPLOYGATE_CLEAN_TASK_NAME, - ) + dependsOn(XcodePlugin.HOCKEYAPP_CLEAN_TASK_NAME, + XcodePlugin.HOCKEYAPP_CLEAN_TASK_NAME, + XcodePlugin.DEPLOYGATE_CLEAN_TASK_NAME) + this.description = "Cleans up the generated files from the previous build" } @TaskAction def clean() { - project.xcodebuild.dstRoot.deleteDir() - project.xcodebuild.objRoot.deleteDir() - project.xcodebuild.symRoot.deleteDir() - project.xcodebuild.sharedPrecompsDir.deleteDir() + project.xcodebuild.dstRoot.get().deleteDir() + project.xcodebuild.objRoot.get().deleteDir() + project.xcodebuild.symRoot.get().deleteDir() + project.xcodebuild.sharedPrecompsDir.get().deleteDir() } } diff --git a/plugin/src/main/groovy/org/openbakery/XcodeBuildPluginExtension.groovy b/plugin/src/main/groovy/org/openbakery/XcodeBuildPluginExtension.groovy index e169cb11..4823ee50 100644 --- a/plugin/src/main/groovy/org/openbakery/XcodeBuildPluginExtension.groovy +++ b/plugin/src/main/groovy/org/openbakery/XcodeBuildPluginExtension.groovy @@ -18,8 +18,12 @@ package org.openbakery import org.apache.commons.io.filefilter.SuffixFileFilter import org.apache.commons.lang.StringUtils import org.gradle.api.Project +import org.gradle.api.Transformer +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property import org.gradle.util.ConfigureUtil -import org.openbakery.signing.Signing +import org.openbakery.extension.Signing import org.openbakery.util.PathHelper import org.openbakery.util.PlistHelper import org.openbakery.util.VariableResolver @@ -27,62 +31,37 @@ import org.openbakery.xcode.* import org.slf4j.Logger import org.slf4j.LoggerFactory -/* - -^ -should be migrated to this -> and renamed to Device - -enum Devices { - PHONE(1<<0), - PAD(1<<1), - WATCH(1<<2), - TV(1<<3) - - private final int value; - - Devices(int value) { - this.value = value - } - - public int getValue() { - return value - } - - public boolean is(Devices device) { - return (this.value & device.value) > 0 - } - -} - */ - - class XcodeBuildPluginExtension { - public final static KEYCHAIN_NAME_BASE = "gradle-" - - private static Logger logger = LoggerFactory.getLogger(XcodeBuildPluginExtension.class) + final Property bitcode = project.objects.property(Boolean) + final Property version = project.objects.property(String) + final Property scheme = project.objects.property(String) + final DirectoryProperty archiveDirectory = project.layout.directoryProperty() + final DirectoryProperty schemeArchiveFile = project.layout.directoryProperty() + final DirectoryProperty dstRoot = project.layout.directoryProperty() + final DirectoryProperty objRoot = project.layout.directoryProperty() + final DirectoryProperty symRoot = project.layout.directoryProperty() + final DirectoryProperty sharedPrecompsDir = project.layout.directoryProperty() + final DirectoryProperty derivedDataPath = project.layout.directoryProperty() + final Property xcodeServiceProperty = project.objects.property(XcodeService) + final Signing signing XcodebuildParameters _parameters = new XcodebuildParameters() String infoPlist = null - String scheme = null + String configuration = 'Debug' boolean simulator = true Type type = Type.iOS - String target = null - Object dstRoot - Object objRoot - Object symRoot - Object sharedPrecompsDir - Object derivedDataPath - Signing signing = null + String target + def additionalParameters = null String bundleNameSuffix = null List arch = null String workspace = null - String xcodeVersion = null + Map environment = null String productName = null String bundleName = null @@ -90,14 +69,11 @@ class XcodeBuildPluginExtension { String ipaFileName = null File projectFile - Boolean bitcode = false - boolean useXcodebuildArchive = false Devices devices = Devices.UNIVERSAL - CommandRunner commandRunner VariableResolver variableResolver PlistHelper plistHelper @@ -110,34 +86,56 @@ class XcodeBuildPluginExtension { * internal parameters */ private final Project project + private final CommandRunner commandRunner + + private static final Logger logger = LoggerFactory.getLogger(XcodeBuildPluginExtension.class) + + XcodeBuildPluginExtension(Project project, + CommandRunner commandRunner) { + this.project = project + this.commandRunner = commandRunner - public XcodeBuildPluginExtension(Project project) { - this.project = project; - this.signing = new Signing(project) - this.variableResolver = new VariableResolver(project) - commandRunner = new CommandRunner() plistHelper = new PlistHelper(commandRunner) - this.dstRoot = { - return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve("dst") - } + configureServices() + configurePaths() - this.objRoot = { - return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve("obj") - } + this.signing = project.objects.newInstance(Signing, project, commandRunner) + this.variableResolver = new VariableResolver(project) - this.symRoot = { - return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve("sym") - } + this.dstRoot.set(project.layout.buildDirectory.dir("dst")) + this.objRoot.set(project.layout.buildDirectory.dir("obj")) + this.symRoot.set(project.layout.buildDirectory.dir("sym")) + this.sharedPrecompsDir.set(project.layout.buildDirectory.dir("shared")) + this.derivedDataPath.set(project.layout.buildDirectory.dir("derivedData")) + } - this.sharedPrecompsDir = { - return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve("shared") - } + private void configureServices() { + XcodeService service = project.objects.newInstance(XcodeService, + project) + service.commandRunnerProperty.set(commandRunner) + this.xcodeServiceProperty.set(service) + } - this.derivedDataPath = { - return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve("derivedData") - } + private void configurePaths() { + this.archiveDirectory.set(project.layout + .buildDirectory + .dir(PathHelper.FOLDER_ARCHIVE)) + + this.schemeArchiveFile.set(scheme.map(new Transformer() { + @Override + Directory transform(String scheme) { + return archiveDirectory.get() + .dir(scheme + PathHelper.EXTENSION_XCARCHIVE) + } + })) + } + Optional getBuildTargetConfiguration(String schemeName, + String configuration) { + return Optional.ofNullable(projectSettings.get(schemeName, null)) + .map { it -> it.buildSettings } + .map { bs -> (BuildConfiguration) bs.get(configuration) } } String getWorkspace() { @@ -151,61 +149,6 @@ class XcodeBuildPluginExtension { return null } - void setDerivedDataPath(File derivedDataPath) { - this.derivedDataPath = derivedDataPath - } - - void setDstRoot(File dstRoot) { - this.dstRoot = dstRoot - } - - void setObjRoot(File objRoot) { - this.objRoot = objRoot - } - - void setSymRoot(File symRoot) { - this.symRoot = symRoot - } - - void setSharedPrecompsDir(File sharedPrecompsDir) { - this.sharedPrecompsDir = sharedPrecompsDir - } - - File getDstRoot() { - if (dstRoot instanceof File) { - return dstRoot - } - return project.file(dstRoot) - } - - File getObjRoot() { - if (objRoot instanceof File) { - return objRoot - } - return project.file(objRoot) - } - - File getSymRoot() { - if (symRoot instanceof File) { - return symRoot - } - return project.file(symRoot) - } - - File getSharedPrecompsDir() { - if (sharedPrecompsDir instanceof File) { - return sharedPrecompsDir - } - return project.file(sharedPrecompsDir) - } - - File getDerivedDataPath() { - if (derivedDataPath instanceof File) { - return derivedDataPath - } - return project.file(derivedDataPath) - } - void signing(Closure closure) { ConfigureUtil.configure(closure, this.signing) } @@ -277,14 +220,6 @@ class XcodeBuildPluginExtension { } - void setVersion(String version) { - this.xcodeVersion = version - // check if the version is valid. On creation of the Xcodebuild class an exception is thrown if the version is not valid - xcode = null - //getXcode() - } - - String getValueFromInfoPlist(key) { if (infoPlist != null) { File infoPlistFile = new File(project.projectDir, infoPlist) @@ -339,7 +274,7 @@ class XcodeBuildPluginExtension { } } } - return new File(getSymRoot(), path) + return new File(getSymRoot().asFile.get(), path) } @@ -421,11 +356,13 @@ class XcodeBuildPluginExtension { return result } - void setType(String type) { this.type = Type.typeFromString(type) } + Type getType() { + return type + } boolean getSimulator() { if (type == Type.macOS) { @@ -465,7 +402,7 @@ class XcodeBuildPluginExtension { // should be remove in the future, so that every task has its own xcode object Xcode getXcode() { if (xcode == null) { - xcode = new Xcode(commandRunner, xcodeVersion) + xcode = new Xcode(commandRunner, version.get()) } logger.debug("using xcode {}", xcode) return xcode @@ -474,21 +411,21 @@ class XcodeBuildPluginExtension { XcodebuildParameters getXcodebuildParameters() { def result = new XcodebuildParameters() - result.scheme = this.scheme + result.scheme = this.scheme.getOrNull() result.target = this.target result.simulator = this.simulator result.type = this.type result.workspace = getWorkspace() result.configuration = this.configuration - result.dstRoot = this.getDstRoot() - result.objRoot = this.getObjRoot() - result.symRoot = this.getSymRoot() - result.sharedPrecompsDir = this.getSharedPrecompsDir() - result.derivedDataPath = this.getDerivedDataPath() + result.dstRoot = this.getDstRoot().asFile.getOrNull() + result.objRoot = this.getObjRoot().asFile.getOrNull() + result.symRoot = this.getSymRoot().asFile.getOrNull() + result.sharedPrecompsDir = this.getSharedPrecompsDir().asFile.getOrNull() + result.derivedDataPath = this.derivedDataPath.asFile.getOrNull() result.additionalParameters = this.additionalParameters result.devices = this.devices result.configuredDestinations = this.destinations - result.bitcode = this.bitcode + result.bitcode = this.bitcode.getOrElse(true) result.applicationBundle = getApplicationBundle() if (this.arch != null) { diff --git a/plugin/src/main/groovy/org/openbakery/XcodeBuildTask.groovy b/plugin/src/main/groovy/org/openbakery/XcodeBuildTask.groovy index fe438758..494b0c34 100644 --- a/plugin/src/main/groovy/org/openbakery/XcodeBuildTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/XcodeBuildTask.groovy @@ -15,7 +15,10 @@ */ package org.openbakery +import org.gradle.api.Task +import org.gradle.api.specs.Spec import org.gradle.api.tasks.TaskAction +import org.openbakery.xcode.Type import org.openbakery.xcode.Xcodebuild class XcodeBuildTask extends AbstractXcodeBuildTask { @@ -28,6 +31,14 @@ class XcodeBuildTask extends AbstractXcodeBuildTask { XcodePlugin.INFOPLIST_MODIFY_TASK_NAME, ) this.description = "Builds the Xcode project" + + onlyIf(new Spec() { + @Override + boolean isSatisfiedBy(Task task) { + return getXcodeExtension().getType() == Type.macOS || + getXcodeExtension().getType() == Type.watchOS + } + }) } @TaskAction diff --git a/plugin/src/main/groovy/org/openbakery/XcodePlugin.groovy b/plugin/src/main/groovy/org/openbakery/XcodePlugin.groovy index 4a8185c7..60727419 100644 --- a/plugin/src/main/groovy/org/openbakery/XcodePlugin.groovy +++ b/plugin/src/main/groovy/org/openbakery/XcodePlugin.groovy @@ -25,13 +25,18 @@ import org.gradle.api.tasks.testing.Test import org.openbakery.appledoc.AppledocCleanTask import org.openbakery.appledoc.AppledocTask import org.openbakery.appstore.AppstorePluginExtension -import org.openbakery.appstore.AppstoreValidateTask import org.openbakery.appstore.AppstoreUploadTask +import org.openbakery.appstore.AppstoreValidateTask +import org.openbakery.archiving.XcodeBuildArchiveTask +import org.openbakery.archiving.XcodeBuildArchiveTaskIosAndTvOS +import org.openbakery.archiving.XcodeBuildLegacyArchiveTask +import org.openbakery.carthage.CarthageBootStrapTask import org.openbakery.carthage.CarthageCleanTask import org.openbakery.carthage.CarthageUpdateTask import org.openbakery.cocoapods.CocoapodsBootstrapTask import org.openbakery.cocoapods.CocoapodsInstallTask import org.openbakery.cocoapods.CocoapodsUpdateTask +import org.openbakery.codesign.Security import org.openbakery.configuration.XcodeConfigTask import org.openbakery.coverage.CoverageCleanTask import org.openbakery.coverage.CoveragePluginExtension @@ -42,31 +47,20 @@ import org.openbakery.crashlytics.CrashlyticsUploadTask import org.openbakery.deploygate.DeployGateCleanTask import org.openbakery.deploygate.DeployGatePluginExtension import org.openbakery.deploygate.DeployGateUploadTask +import org.openbakery.extension.Signing import org.openbakery.hockeyapp.HockeyAppCleanTask import org.openbakery.hockeyapp.HockeyAppPluginExtension import org.openbakery.hockeyapp.HockeyAppUploadTask -import org.openbakery.hockeykit.HockeyKitArchiveTask -import org.openbakery.hockeykit.HockeyKitCleanTask -import org.openbakery.hockeykit.HockeyKitImageTask -import org.openbakery.hockeykit.HockeyKitManifestTask -import org.openbakery.hockeykit.HockeyKitPluginExtension -import org.openbakery.hockeykit.HockeyKitReleaseNotesTask +import org.openbakery.hockeykit.* import org.openbakery.oclint.OCLintPluginExtension import org.openbakery.oclint.OCLintTask -import org.openbakery.packaging.ReleaseNotesTask -import org.openbakery.signing.KeychainCleanupTask -import org.openbakery.signing.KeychainCreateTask +import org.openbakery.packaging.PackageLegacyTask import org.openbakery.packaging.PackageTask -import org.openbakery.signing.KeychainRemoveFromSearchListTask -import org.openbakery.signing.ProvisioningCleanupTask -import org.openbakery.signing.ProvisioningInstallTask -import org.openbakery.simulators.SimulatorKillTask -import org.openbakery.simulators.SimulatorsCleanTask -import org.openbakery.simulators.SimulatorsCreateTask -import org.openbakery.simulators.SimulatorsListTask -import org.openbakery.simulators.SimulatorStartTask -import org.openbakery.simulators.SimulatorRunAppTask -import org.openbakery.simulators.SimulatorInstallAppTask +import org.openbakery.packaging.PackageTaskIosAndTvOS +import org.openbakery.signing.* +import org.openbakery.simulators.* +import org.openbakery.util.PlistHelper +import org.openbakery.xcode.Xcode import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -90,8 +84,7 @@ class XcodePlugin implements Plugin { public static final String XCODE_TEST_TASK_NAME = "xcodetest" public static final String XCODE_BUILD_FOR_TEST_TASK_NAME = "xcodebuildForTest" - public static final String XCODE_TEST_RUN_TASK_NAME = "xcodetestrun" - public static final String ARCHIVE_TASK_NAME = "archive" + public static final String XCODE_TEST_RUN_TASK_NAME = "xcodetestrun" public static final String SIMULATORS_LIST_TASK_NAME = "simulatorsList" public static final String SIMULATORS_CREATE_TASK_NAME = "simulatorsCreate" public static final String SIMULATORS_CLEAN_TASK_NAME = "simulatorsClean" @@ -108,13 +101,7 @@ class XcodePlugin implements Plugin { public static final String HOCKEYKIT_IMAGE_TASK_NAME = "hockeykitImage" public static final String HOCKEYKIT_CLEAN_TASK_NAME = "hockeykitClean" public static final String HOCKEYKIT_TASK_NAME = "hockeykit" - public static final String KEYCHAIN_CREATE_TASK_NAME = "keychainCreate" - public static final String KEYCHAIN_CLEAN_TASK_NAME = "keychainClean" - public static final String KEYCHAIN_REMOVE_SEARCH_LIST_TASK_NAME = "keychainRemove" public static final String INFOPLIST_MODIFY_TASK_NAME = 'infoplistModify' - public static final String PROVISIONING_INSTALL_TASK_NAME = 'provisioningInstall' - public static final String PROVISIONING_CLEAN_TASK_NAME = 'provisioningClean' - public static final String PACKAGE_TASK_NAME = 'package' public static final String PACKAGE_RELEASE_NOTES_TASK_NAME = 'packageReleaseNotes' public static final String APPSTORE_UPLOAD_TASK_NAME = 'appstoreUpload' public static final String APPSTORE_VALIDATE_TASK_NAME = 'appstoreValidate' @@ -129,6 +116,7 @@ class XcodePlugin implements Plugin { public static final String OCLINT_TASK_NAME = 'oclint' public static final String OCLINT_REPORT_TASK_NAME = 'oclintReport' public static final String CPD_TASK_NAME = 'cpd' + public static final String CARTHAGE_BOOTSTRAP_TASK_NAME = 'carthageBootstrap' public static final String CARTHAGE_UPDATE_TASK_NAME = 'carthageUpdate' public static final String CARTHAGE_CLEAN_TASK_NAME = 'carthageClean' @@ -143,13 +131,20 @@ class XcodePlugin implements Plugin { public static final String SDK_IPHONESIMULATOR = "iphonesimulator" + private Signing signingExtension + private XcodeBuildPluginExtension xcodeBuildPluginExtension + private InfoPlistExtension infoPlistExtension + private CommandRunner commandRunner + private PlistHelper plistHelper + private Security securityTool + private Xcode xcode void apply(Project project) { project.getPlugins().apply(BasePlugin.class); - System.setProperty("java.awt.headless", "true"); - + System.setProperty("java.awt.headless", "true") + setupTools(project) configureExtensions(project) configureClean(project) configureBuild(project) @@ -174,6 +169,12 @@ class XcodePlugin implements Plugin { configureProperties(project) } + void setupTools(Project project) { + this.commandRunner = new CommandRunner() + this.xcode = new Xcode(commandRunner) + this.plistHelper = new PlistHelper(commandRunner) + this.securityTool = new Security(commandRunner) + } void configureProperties(Project project) { @@ -434,10 +435,18 @@ class XcodePlugin implements Plugin { } - void configureExtensions(Project project) { - project.extensions.create("xcodebuild", XcodeBuildPluginExtension, project) - project.extensions.create("infoplist", InfoPlistExtension) + this.xcodeBuildPluginExtension = project.extensions.create("xcodebuild", + XcodeBuildPluginExtension, + project, + commandRunner) + + this.signingExtension = xcodeBuildPluginExtension.signing + + this.infoPlistExtension = project.extensions.create("infoplist", + InfoPlistExtension, + project) + project.extensions.create("hockeykit", HockeyKitPluginExtension, project) project.extensions.create("appstore", AppstorePluginExtension, project) project.extensions.create("hockeyapp", HockeyAppPluginExtension, project) @@ -451,8 +460,7 @@ class XcodePlugin implements Plugin { private void configureTestRunDependencies(Project project) { for (XcodeTestRunTask xcodeTestRunTask : project.getTasks().withType(XcodeTestRunTask.class)) { if (xcodeTestRunTask.runOnDevice()) { - xcodeTestRunTask.dependsOn(XcodePlugin.KEYCHAIN_CREATE_TASK_NAME, XcodePlugin.PROVISIONING_INSTALL_TASK_NAME) - xcodeTestRunTask.finalizedBy(XcodePlugin.KEYCHAIN_REMOVE_SEARCH_LIST_TASK_NAME) + xcodeTestRunTask.dependsOn(KeychainCreateTask.TASK_NAME, ProvisioningInstallTask.TASK_NAME) } } } @@ -476,17 +484,52 @@ class XcodePlugin implements Plugin { } private void configureArchive(Project project) { - XcodeBuildArchiveTask xcodeBuildArchiveTask = project.getTasks().create(ARCHIVE_TASK_NAME, XcodeBuildArchiveTask.class); - xcodeBuildArchiveTask.setGroup(XCODE_GROUP_NAME); - //xcodeBuildArchiveTask.dependsOn(project.getTasks().getByName(BasePlugin.CLEAN_TASK_NAME)); + project.getTasks() + .create(PrepareXcodeArchivingTask.NAME, + PrepareXcodeArchivingTask.class) { + it.group = XCODE_GROUP_NAME + + it.certificateFriendlyName.set(signingExtension.certificateFriendlyName) + it.commandRunnerProperty.set(commandRunner) + it.configurationBundleIdentifier.set(infoPlistExtension.configurationBundleIdentifier) + it.entitlementsFile.set(signingExtension.entitlementsFile) + it.outputFile.set(signingExtension.xcConfigFile) + it.plistHelperProperty.set(plistHelper) + it.registeredProvisioningFiles.set(signingExtension.registeredProvisioningFiles) + } + + project.getTasks().create(XcodeBuildArchiveTaskIosAndTvOS.NAME, + XcodeBuildArchiveTaskIosAndTvOS.class) { + it.setGroup(XCODE_GROUP_NAME) + it.buildType.set(xcodeBuildPluginExtension.type) + it.commandRunnerProperty.set(commandRunner) + it.outputArchiveFile.set(xcodeBuildPluginExtension.schemeArchiveFile) + it.scheme.set(xcodeBuildPluginExtension.scheme) + it.xcode.set(xcode) + it.xcodeVersion.set(xcodeBuildPluginExtension.version) + it.xcConfigFile.set(signingExtension.xcConfigFile) + it.xcodeServiceProperty.set(xcodeBuildPluginExtension.xcodeServiceProperty) + } + + XcodeBuildLegacyArchiveTask xcodeBuildArchiveTask = project.getTasks(). + create(XcodeBuildLegacyArchiveTask.NAME, XcodeBuildLegacyArchiveTask.class) + xcodeBuildArchiveTask.setGroup(XCODE_GROUP_NAME) + + XcodeBuildArchiveTask task = project.tasks.create(XcodeBuildArchiveTask.NAME, + XcodeBuildArchiveTask.class) + task.setGroup(XCODE_GROUP_NAME) } private void configureSimulatorTasks(Project project) { project.task(SIMULATORS_LIST_TASK_NAME, type: SimulatorsListTask, group: SIMULATORS_LIST_TASK_NAME) project.task(SIMULATORS_CREATE_TASK_NAME, type: SimulatorsCreateTask, group: SIMULATORS_LIST_TASK_NAME) project.task(SIMULATORS_CLEAN_TASK_NAME, type: SimulatorsCleanTask, group: SIMULATORS_LIST_TASK_NAME) - project.task(SIMULATORS_START_TASK_NAME, type: SimulatorStartTask, group: SIMULATORS_LIST_TASK_NAME) + + project.tasks.create(SIMULATORS_START_TASK_NAME, SimulatorStartTask) { + it.group = SIMULATORS_LIST_TASK_NAME + } + project.task(SIMULATORS_RUN_APP_TASK_NAME, type: SimulatorRunAppTask, group: SIMULATORS_LIST_TASK_NAME) project.task(SIMULATORS_INSTALL_APP_TASK_NAME, type: SimulatorInstallAppTask, group: SIMULATORS_LIST_TASK_NAME) project.task(SIMULATORS_KILL_TASK_NAME, type: SimulatorKillTask, group: SIMULATORS_LIST_TASK_NAME) @@ -504,9 +547,20 @@ class XcodePlugin implements Plugin { } private void configureKeychain(Project project) { - project.task(KEYCHAIN_CREATE_TASK_NAME, type: KeychainCreateTask, group: XCODE_GROUP_NAME) - project.task(KEYCHAIN_CLEAN_TASK_NAME, type: KeychainCleanupTask, group: XCODE_GROUP_NAME) - project.task(KEYCHAIN_REMOVE_SEARCH_LIST_TASK_NAME, type: KeychainRemoveFromSearchListTask, group: XCODE_GROUP_NAME) + project.tasks.create(KeychainCreateTask.TASK_NAME, KeychainCreateTask.class) { + it.group = XCODE_GROUP_NAME + + it.certificateFile.set(signingExtension.certificate) + it.certificateUri.set(signingExtension.certificateURI) + it.certificatePassword.set(signingExtension.certificatePassword) + it.outputDirectory.set(signingExtension.signingDestinationRoot) + it.keyChainFile.set(signingExtension.keyChainFile) + it.keychainTimeout.set(signingExtension.timeout) + it.security.set(securityTool) + it.commandRunnerProperty.set(commandRunner) + + signingExtension.certificateFriendlyName.set(it.certificateFriendlyName) + } } private void configureTest(Project project) { @@ -517,34 +571,75 @@ class XcodePlugin implements Plugin { } private configureInfoPlist(Project project) { - project.task(INFOPLIST_MODIFY_TASK_NAME, type: InfoPlistModifyTask, group: XCODE_GROUP_NAME) + project.tasks.create(INFOPLIST_MODIFY_TASK_NAME, + InfoPlistModifyTask) { + it.group = XCODE_GROUP_NAME + infoPlistExtension.configurationBundleIdentifier.set(it.configurationBundleIdentifier) + } } private configureProvisioning(Project project) { - project.task(PROVISIONING_INSTALL_TASK_NAME, type: ProvisioningInstallTask, group: XCODE_GROUP_NAME) - project.task(PROVISIONING_CLEAN_TASK_NAME, type: ProvisioningCleanupTask, group: XCODE_GROUP_NAME) + project.tasks.create(ProvisioningInstallTask.TASK_NAME, + ProvisioningInstallTask) { + it.group = XCODE_GROUP_NAME + + it.commandRunnerProperty.set(commandRunner) + it.mobileProvisioningList.set(signingExtension.mobileProvisionList) + it.outputDirectory.set(signingExtension.provisioningDestinationRoot) + it.plistHelperProperty.set(plistHelper) + it.plistHelperProperty.set(plistHelper) + + // We use the result of the task to populate the read only values + signingExtension + .registeredProvisioningFiles + .set(it.registeredProvisioning) + + signingExtension + .registeredProvisioning + .set(it.registeredProvisioningFiles) + } } private configurePackage(Project project) { - PackageTask packageTask = project.task(PACKAGE_TASK_NAME, type: PackageTask, group: XCODE_GROUP_NAME) - + configureModernPackager(project) + configureLegacyPackager(project) - project.task(PACKAGE_RELEASE_NOTES_TASK_NAME, type: ReleaseNotesTask, group: XCODE_GROUP_NAME) + project.task(PackageTask.NAME, + type: PackageTask.class, + group: XCODE_GROUP_NAME) - //ProvisioningCleanupTask provisioningCleanup = project.getTasks().getByName(PROVISIONING_CLEAN_TASK_NAME) + project.task(PACKAGE_RELEASE_NOTES_TASK_NAME, + type: ReleaseNotesTask, + group: XCODE_GROUP_NAME) - //KeychainCleanupTask keychainCleanupTask = project.getTasks().getByName(KEYCHAIN_CLEAN_TASK_NAME) + } -/* // disabled clean because of #115 - packageTask.doLast { - provisioningCleanup.clean() - keychainCleanupTask.clean() + private void configureModernPackager(Project project) { + project.tasks.create(PackageTaskIosAndTvOS.NAME, + PackageTaskIosAndTvOS) { + it.group = XCODE_GROUP_NAME + + it.bitCode.set(xcodeBuildPluginExtension.bitcode) + it.buildType.set(xcodeBuildPluginExtension.type) + it.certificateFriendlyName.set(signingExtension.certificateFriendlyName) + it.commandRunner.set(this.commandRunner) + it.plistHelper.set(xcodeBuildPluginExtension.plistHelper) + it.registeredProvisioningFiles.set(signingExtension.registeredProvisioning) + it.scheme.set(xcodeBuildPluginExtension.scheme) + it.signingMethod.set(signingExtension.signingMethod) } -*/ + } + + private void configureLegacyPackager(Project project) { + PackageLegacyTask packageTask = project.task(PackageLegacyTask.NAME, + type: PackageLegacyTask, + group: XCODE_GROUP_NAME) + XcodeBuildTask xcodeBuildTask = project.getTasks().getByName(XCODE_BUILD_TASK_NAME) packageTask.shouldRunAfter(xcodeBuildTask) } + private configureAppstore(Project project) { project.task(APPSTORE_UPLOAD_TASK_NAME, type: AppstoreUploadTask, group: APPSTORE_GROUP_NAME) project.task(APPSTORE_VALIDATE_TASK_NAME, type: AppstoreValidateTask, group: APPSTORE_GROUP_NAME) @@ -597,16 +692,16 @@ class XcodePlugin implements Plugin { private void configureCarthage(Project project) { project.task(CARTHAGE_CLEAN_TASK_NAME, type: CarthageCleanTask, group: CARTHAGE_GROUP_NAME) project.task(CARTHAGE_UPDATE_TASK_NAME, type: CarthageUpdateTask, group: CARTHAGE_GROUP_NAME) + project.task(CARTHAGE_BOOTSTRAP_TASK_NAME, type: CarthageBootStrapTask, group: CARTHAGE_GROUP_NAME) } private configureCarthageDependencies(Project project) { - CarthageUpdateTask carthageUpdateTask = project.getTasks().getByName(XcodePlugin.CARTHAGE_UPDATE_TASK_NAME) - CarthageCleanTask carthageCleanTask = project.getTasks().getByName(XcodePlugin.CARTHAGE_CLEAN_TASK_NAME) + CarthageBootStrapTask bootStrapTask = project.getTasks().getByName(CARTHAGE_BOOTSTRAP_TASK_NAME) + addDependencyToBuild(project, bootStrapTask) - if (carthageUpdateTask.hasCartFile()) { - addDependencyToBuild(project, carthageUpdateTask) - project.getTasks().getByName(BasePlugin.CLEAN_TASK_NAME).dependsOn(carthageCleanTask); - } + project.getTasks() + .getByName(BasePlugin.CLEAN_TASK_NAME) + .dependsOn(project.getTasks().getByName(CARTHAGE_CLEAN_TASK_NAME)) } private void configureOCLint(Project project) { @@ -614,7 +709,7 @@ class XcodePlugin implements Plugin { Task ocLintTask = project.getTasks().create(OCLINT_TASK_NAME); ocLintTask.group = ANALYTICS_GROUP_NAME - ocLintTask.description = "Runs: " + BasePlugin.CLEAN_TASK_NAME + " " + XCODE_BUILD_TASK_NAME + " " + OCLINT_REPORT_TASK_NAME + ocLintTask.description = "Runs: " + BasePlugin.CLEAN_TASK_NAME + " " + XCODE_BUILD_TASK_NAME + " " + OCLINT_REPORT_TASK_NAME ocLintTask.dependsOn(project.getTasks().getByName(BasePlugin.CLEAN_TASK_NAME)) XcodeBuildTask xcodeBuildTask = project.getTasks().getByName(XcodePlugin.XCODE_BUILD_TASK_NAME) diff --git a/plugin/src/main/groovy/org/openbakery/XcodeService.groovy b/plugin/src/main/groovy/org/openbakery/XcodeService.groovy new file mode 100644 index 00000000..4b26a96b --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/XcodeService.groovy @@ -0,0 +1,93 @@ +package org.openbakery + +import org.gradle.api.Project +import org.gradle.api.Transformer +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.openbakery.XcodeService.XcodeApp +import org.openbakery.xcode.Version + +import javax.inject.Inject +import java.util.regex.Matcher +import java.util.regex.Pattern + +class XcodeService { + + final Property commandRunnerProperty + final ListProperty installedXcodes + + private static final String CONTENT_DEVELOPER = "Contents/Developer" + private static final String CONTENT_XCODE_BUILD = "$CONTENT_DEVELOPER/usr/bin/xcodebuild" + private static final Pattern VERSION_PATTERN = ~/Xcode\s([^\s]*)\nBuild\sversion\s([^\s]*)/ + + @Inject + XcodeService(Project project) { + commandRunnerProperty = project.objects.property(CommandRunner) + + installedXcodes = project.objects.listProperty(XcodeApp) + installedXcodes.set(commandRunnerProperty.map(new Transformer, CommandRunner>() { + @Override + List transform(CommandRunner commandRunner) { + return commandRunner.runWithResult("mdfind", + "kMDItemCFBundleIdentifier=com.apple.dt.Xcode") + .split("\n") + .collect { new File(it) } + .collect { new XcodeApp(it, getXcodeVersion(commandRunner, it)) } + } + })) + } + + public XcodeApp getInstallationForVersion(final String version) { + return installedXcodes.map(new Transformer>() { + @Override + XcodeApp transform(List xcodeApps) { + return xcodeApps.find { it.version.toString().startsWith(version) } + } + }).getOrNull() + } + + private Version getXcodeVersion(CommandRunner commandRunner, + File file) { + String xcodeVersion = commandRunner.runWithResult( + new File(file, CONTENT_XCODE_BUILD).absolutePath, + "-version") + + Matcher matcher = VERSION_PATTERN.matcher(xcodeVersion) + if (matcher.matches()) { + Version version = new Version(matcher.group(1)) + version.suffix = matcher.group(2) + + return version + } + + return null + } + + static class XcodeApp implements Serializable { + private final File file + private final Version version + + XcodeApp(File file, + Version version) { + this.file = file + this.version = version + } + + File getFile() { + return file + } + + File getContentXcodeBuildFile() { + return new File(file, CONTENT_XCODE_BUILD) + } + + File getContentDeveloperFile() { + return new File(file, CONTENT_DEVELOPER) + } + + Version getVersion() { + return version + } + } + +} diff --git a/plugin/src/main/groovy/org/openbakery/XcodeTestRunTask.groovy b/plugin/src/main/groovy/org/openbakery/XcodeTestRunTask.groovy index 9b601274..91e70770 100644 --- a/plugin/src/main/groovy/org/openbakery/XcodeTestRunTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/XcodeTestRunTask.groovy @@ -152,7 +152,7 @@ class XcodeTestRunTask extends AbstractXcodeBuildTask { CodesignParameters parameters = new CodesignParameters() parameters.signingIdentity = getSigningIdentity() parameters.keychain = project.xcodebuild.signing.keychainPathInternal - parameters.mobileProvisionFiles = project.xcodebuild.signing.mobileProvisionFile + parameters.mobileProvisionFiles = project.extensions.getByType(XcodeBuildPluginExtension).signing.registeredProvisioningFiles.getFiles().asList() parameters.type = project.xcodebuild.type codesign = new Codesign(xcode, parameters, commandRunner, plistHelper) if (project.xcodebuild.signing.hasEntitlementsFile()) { diff --git a/plugin/src/main/groovy/org/openbakery/archiving/XcodeBuildArchiveTask.groovy b/plugin/src/main/groovy/org/openbakery/archiving/XcodeBuildArchiveTask.groovy new file mode 100644 index 00000000..55067054 --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/archiving/XcodeBuildArchiveTask.groovy @@ -0,0 +1,17 @@ +package org.openbakery.archiving + +import org.openbakery.AbstractXcodeBuildTask + +class XcodeBuildArchiveTask extends AbstractXcodeBuildTask { + + public static final String NAME = "archive" + + XcodeBuildArchiveTask() { + super() + + dependsOn(XcodeBuildArchiveTaskIosAndTvOS.NAME, + XcodeBuildLegacyArchiveTask.NAME) + + setDescription("Archive and export the project") + } +} diff --git a/plugin/src/main/groovy/org/openbakery/archiving/XcodeBuildArchiveTaskIosAndTvOS.groovy b/plugin/src/main/groovy/org/openbakery/archiving/XcodeBuildArchiveTaskIosAndTvOS.groovy new file mode 100644 index 00000000..f1ebe023 --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/archiving/XcodeBuildArchiveTaskIosAndTvOS.groovy @@ -0,0 +1,98 @@ +package org.openbakery.archiving + +import groovy.transform.CompileStatic +import org.gradle.api.DefaultTask +import org.gradle.api.Task +import org.gradle.api.Transformer +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.specs.Spec +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.openbakery.CommandRunner +import org.openbakery.PrepareXcodeArchivingTask +import org.openbakery.XcodeService +import org.openbakery.signing.ProvisioningInstallTask +import org.openbakery.xcode.Type +import org.openbakery.xcode.Xcode +import org.openbakery.xcode.Xcodebuild + +@CompileStatic +class XcodeBuildArchiveTaskIosAndTvOS extends DefaultTask { + + @Input + final Provider xcodeVersion = project.objects.property(String) + + @Input + final Provider scheme = project.objects.property(String) + + @Input + final Provider buildType = project.objects.property(Type) + + @InputFile + final Property xcConfigFile = newInputFile() + + @OutputDirectory + final Provider outputArchiveFile = newOutputDirectory() + + final Property xcodeServiceProperty = project.objects.property(XcodeService) + final Property xcode = project.objects.property(Xcode) + final Property commandRunnerProperty = project.objects.property(CommandRunner) + + public static final String NAME = "archiveXcodeBuild" + + XcodeBuildArchiveTaskIosAndTvOS() { + super() + + dependsOn(ProvisioningInstallTask.TASK_NAME) + dependsOn(PrepareXcodeArchivingTask.NAME) + + this.description = "Use the XcodeBuild archive command line to create the project archive" + + onlyIf(new Spec() { + @Override + boolean isSatisfiedBy(Task task) { + return buildType.get() == Type.iOS || buildType.get() == Type.tvOS + } + }) + } + + @TaskAction + void archive() { + assert scheme.present: "No target scheme configured" + assert outputArchiveFile.present: "No output file folder configured" + assert xcConfigFile.present: "No 'xcconfig' file configured" + + logger.lifecycle("Archive project with configuration: " + + "\n\tScheme : ${scheme.get()} " + + "\n\tXcode version : ${xcodeVersion.getOrElse("System default")}") + + + Xcodebuild.archive(commandRunnerProperty.get(), + scheme.get(), + outputArchiveFile.get().asFile, + xcConfigFile.get().asFile, + getXcodeAppForConfiguration().getOrNull()) + } + + private Provider getXcodeAppForConfiguration() { + + Provider xcodeApp + if (xcodeVersion.present) { + xcodeApp = xcodeServiceProperty.map(new Transformer() { + @Override + File transform(XcodeService xcodeService) { + XcodeService.XcodeApp app = xcodeService.getInstallationForVersion(xcodeVersion.get()) + return app.contentDeveloperFile + } + }) + } else { + xcodeApp = project.objects.property(File) + } + return xcodeApp + } +} diff --git a/plugin/src/main/groovy/org/openbakery/XcodeBuildArchiveTask.groovy b/plugin/src/main/groovy/org/openbakery/archiving/XcodeBuildLegacyArchiveTask.groovy similarity index 91% rename from plugin/src/main/groovy/org/openbakery/XcodeBuildArchiveTask.groovy rename to plugin/src/main/groovy/org/openbakery/archiving/XcodeBuildLegacyArchiveTask.groovy index b4b56eb5..41cd5080 100644 --- a/plugin/src/main/groovy/org/openbakery/XcodeBuildArchiveTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/archiving/XcodeBuildLegacyArchiveTask.groovy @@ -13,34 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.openbakery +package org.openbakery.archiving import groovy.io.FileType import org.apache.commons.io.FileUtils +import org.gradle.api.Task +import org.gradle.api.specs.Spec import org.gradle.api.tasks.TaskAction +import org.openbakery.AbstractXcodeBuildTask +import org.openbakery.BuildConfiguration +import org.openbakery.CommandRunnerException +import org.openbakery.XcodeBuildPluginExtension +import org.openbakery.XcodePlugin import org.openbakery.codesign.ProvisioningProfileReader -import org.openbakery.xcode.Type +import org.openbakery.signing.ProvisioningInstallTask +import org.openbakery.util.PathHelper import org.openbakery.xcode.Extension +import org.openbakery.xcode.Type import org.openbakery.xcode.Xcodebuild import static groovy.io.FileType.FILES -class XcodeBuildArchiveTask extends AbstractXcodeBuildTask { +class XcodeBuildLegacyArchiveTask extends AbstractXcodeBuildTask { - public static final String ARCHIVE_FOLDER = "archive" + public static final String NAME = "archiveLegacy" - XcodeBuildArchiveTask() { + XcodeBuildLegacyArchiveTask() { super() - dependsOn(XcodePlugin.XCODE_BUILD_TASK_NAME) - // when creating an xcarchive for iOS then the provisioning profile is need for the team id so that the entitlements is setup properly - dependsOn(XcodePlugin.PROVISIONING_INSTALL_TASK_NAME) - this.description = "Prepare the app bundle that it can be archive" + this.description = "Use the legacy archiver to create the project archive" + + onlyIf(new Spec() { + @Override + boolean isSatisfiedBy(Task task) { + return getXcodeExtension().getType() == Type.macOS || + getXcodeExtension().getType() == Type.watchOS + } + }) + + dependsOn(XcodePlugin.XCODE_BUILD_TASK_NAME, + ProvisioningInstallTask.TASK_NAME) } def getOutputDirectory() { - def archiveDirectory = new File(project.getBuildDir(), ARCHIVE_FOLDER) + def archiveDirectory = PathHelper.resolveArchiveFolder(project) archiveDirectory.mkdirs() return archiveDirectory } @@ -305,8 +322,13 @@ class XcodeBuildArchiveTask extends AbstractXcodeBuildTask { return } - String applicationIdentifier = "UNKNOWN00ID"; // if UNKNOWN00ID this means that not application identifier is found an this value is used as fallback - File provisioningProfile = ProvisioningProfileReader.getProvisionFileForIdentifier(bundleIdentifier, project.xcodebuild.signing.mobileProvisionFile, this.commandRunner, this.plistHelper) + String applicationIdentifier = "UNKNOWN00ID"; + + // if UNKNOWN00ID this means that not application identifier is found an this value is used as fallback + File provisioningProfile = ProvisioningProfileReader.getProvisionFileForIdentifier(bundleIdentifier, + project.extensions.getByType(XcodeBuildPluginExtension).signing.registeredProvisioningFiles.getFiles().asList(), + this.commandRunner, + this.plistHelper) if (provisioningProfile != null && provisioningProfile.exists()) { ProvisioningProfileReader reader = new ProvisioningProfileReader(provisioningProfile, commandRunner) applicationIdentifier = reader.getApplicationIdentifierPrefix() @@ -353,6 +375,7 @@ class XcodeBuildArchiveTask extends AbstractXcodeBuildTask { @TaskAction def archive() { + println "archive" parameters = project.xcodebuild.xcodebuildParameters.merge(parameters) if (parameters.isSimulatorBuildOf(Type.iOS) || parameters.isSimulatorBuildOf(Type.tvOS)) { logger.debug("Create zip archive") @@ -441,7 +464,7 @@ class XcodeBuildArchiveTask extends AbstractXcodeBuildTask { } - // TODO: Define a `exportOptionsPlist` to avoid that kind of issue + // TODO: Define a `exportOptionsPlist` to avoid that kind of issue def removeUnneededDylibsFromBundle(File bundle) { File libswiftRemoteMirror = new File(bundle, "libswiftRemoteMirror.dylib") if (libswiftRemoteMirror.exists()) { @@ -512,8 +535,7 @@ class XcodeBuildArchiveTask extends AbstractXcodeBuildTask { File getArchiveDirectory() { - - def archiveDirectoryName = XcodeBuildArchiveTask.ARCHIVE_FOLDER + "/" + project.xcodebuild.bundleName + def archiveDirectoryName = PathHelper.FOLDER_ARCHIVE + "/" + project.xcodebuild.bundleName if (project.xcodebuild.bundleNameSuffix != null) { archiveDirectoryName += project.xcodebuild.bundleNameSuffix diff --git a/plugin/src/main/groovy/org/openbakery/carthage/AbstractCarthageTaskBase.groovy b/plugin/src/main/groovy/org/openbakery/carthage/AbstractCarthageTaskBase.groovy new file mode 100644 index 00000000..abbb6664 --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/carthage/AbstractCarthageTaskBase.groovy @@ -0,0 +1,95 @@ +package org.openbakery.carthage + +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* +import org.openbakery.AbstractXcodeTask +import org.openbakery.xcode.Type + +abstract class AbstractCarthageTaskBase extends AbstractXcodeTask { + + static final String ACTION_BOOTSTRAP = "bootstrap" + static final String ACTION_UPDATE = "update" + static final String ARG_CACHE_BUILDS = "--cache-builds" + static final String ARG_PLATFORM = "--platform" + static final String CARTHAGE_FILE = "Cartfile" + static final String CARTHAGE_FILE_RESOLVED = "Cartfile.resolved" + static final String CARTHAGE_PLATFORM_IOS = "iOS" + static final String CARTHAGE_PLATFORM_MACOS = "Mac" + static final String CARTHAGE_PLATFORM_TVOS = "tvOS" + static final String CARTHAGE_PLATFORM_WATCHOS = "watchOS" + static final String CARTHAGE_USR_BIN_PATH = "/usr/local/bin/carthage" + + AbstractCarthageTaskBase() { + super() + } + + @Input + @Optional + String getRequiredXcodeVersion() { + return getProjectXcodeVersion() + } + + @InputFile + @Optional + @PathSensitive(PathSensitivity.RELATIVE) + Provider getCartFile() { + // Cf https://github.com/gradle/gradle/issues/2016 + File file = project.rootProject.file(CARTHAGE_FILE) + return project.provider { + file.exists() ? file + : File.createTempFile(CARTHAGE_FILE, "") + } + } + + @InputFile + @Optional + @PathSensitive(PathSensitivity.RELATIVE) + Provider getCartResolvedFile() { + // Cf https://github.com/gradle/gradle/issues/2016 + File file = project.rootProject.file(CARTHAGE_FILE_RESOLVED) + return project.provider { + file.exists() ? file + : File.createTempFile(CARTHAGE_FILE_RESOLVED, "resolved") + } + } + + @Input + String getCarthagePlatformName() { + switch (project.xcodebuild.type) { + case Type.iOS: return CARTHAGE_PLATFORM_IOS + case Type.tvOS: return CARTHAGE_PLATFORM_TVOS + case Type.macOS: return CARTHAGE_PLATFORM_MACOS + case Type.watchOS: return CARTHAGE_PLATFORM_WATCHOS + default: return 'all' + } + } + + @OutputDirectory + Provider getOutputDirectory() { + return project.provider { + project.rootProject.file("Carthage/Build/" + getCarthagePlatformName()) + } + } + + String getCarthageCommand() { + try { + return commandRunner.runWithResult("which", "carthage") + } catch (CommandRunnerException) { + // ignore, because try again with full path below + } + + try { + commandRunner.runWithResult("ls", CARTHAGE_USR_BIN_PATH) + return CARTHAGE_USR_BIN_PATH + } catch (CommandRunnerException) { + // ignore, because blow an exception is thrown + } + throw new IllegalStateException("The carthage command was not found. Make sure that Carthage is installed") + } + + boolean hasCartFile() { + return project.rootProject + .file(CARTHAGE_FILE) + .exists() + } +} diff --git a/plugin/src/main/groovy/org/openbakery/carthage/CarthageBootStrapTask.groovy b/plugin/src/main/groovy/org/openbakery/carthage/CarthageBootStrapTask.groovy new file mode 100644 index 00000000..7c6e8c43 --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/carthage/CarthageBootStrapTask.groovy @@ -0,0 +1,35 @@ +package org.openbakery.carthage + +import groovy.transform.CompileStatic +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.openbakery.output.ConsoleOutputAppender + +@CompileStatic +class CarthageBootStrapTask extends AbstractCarthageTaskBase { + + CarthageBootStrapTask() { + super() + setDescription "Check out and build the Carthage project dependencies" + } + + @TaskAction + void update() { + if (hasCartFile()) { + logger.info('Boostrap Carthage for platform ' + carthagePlatformName) + def output = services.get(StyledTextOutputFactory) + .create(CarthageBootStrapTask) + + List args = [getCarthageCommand(), + ACTION_BOOTSTRAP, + ARG_PLATFORM, + carthagePlatformName, + ARG_CACHE_BUILDS] + + commandRunner.run(project.projectDir.absolutePath, + args, + getRequiredXcodeVersion() != null ? xcode.getXcodeSelectEnvValue(getRequiredXcodeVersion()) : null, + new ConsoleOutputAppender(output)) + } + } +} diff --git a/plugin/src/main/groovy/org/openbakery/carthage/CarthageCleanTask.groovy b/plugin/src/main/groovy/org/openbakery/carthage/CarthageCleanTask.groovy index 19ead8e6..43ec92e6 100644 --- a/plugin/src/main/groovy/org/openbakery/carthage/CarthageCleanTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/carthage/CarthageCleanTask.groovy @@ -14,6 +14,4 @@ class CarthageCleanTask extends DefaultTask { def clean() { project.file("Carthage").deleteDir() } - - } diff --git a/plugin/src/main/groovy/org/openbakery/carthage/CarthageUpdateTask.groovy b/plugin/src/main/groovy/org/openbakery/carthage/CarthageUpdateTask.groovy index 60d38bf7..bc742f4a 100644 --- a/plugin/src/main/groovy/org/openbakery/carthage/CarthageUpdateTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/carthage/CarthageUpdateTask.groovy @@ -1,71 +1,14 @@ package org.openbakery.carthage -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* +import org.gradle.api.tasks.TaskAction import org.gradle.internal.logging.text.StyledTextOutputFactory -import org.openbakery.AbstractXcodeTask import org.openbakery.output.ConsoleOutputAppender -import org.openbakery.xcode.Type -class CarthageUpdateTask extends AbstractXcodeTask { - - static final String ACTION_UPDATE = "update" - static final String ARG_CACHE_BUILDS = "--cache-builds" - static final String ARG_PLATFORM = "--platform" - static final String CARTHAGE_FILE = "Cartfile" - static final String CARTHAGE_FILE_RESOLVED = "Cartfile.resolved" - static final String CARTHAGE_PLATFORM_IOS = "iOS" - static final String CARTHAGE_PLATFORM_MACOS = "Mac" - static final String CARTHAGE_PLATFORM_TVOS = "tvOS" - static final String CARTHAGE_PLATFORM_WATCHOS = "watchOS" - static final String CARTHAGE_USR_BIN_PATH = "/usr/local/bin/carthage" +class CarthageUpdateTask extends AbstractCarthageTaskBase { CarthageUpdateTask() { super() - newOutputDirectory() - setDescription "Installs the carthage dependencies for the given project" - } - - @InputFile - @Optional - @PathSensitive(PathSensitivity.RELATIVE) - Provider getCartFile() { - // Cf https://github.com/gradle/gradle/issues/2016 - File file = project.rootProject.file(CARTHAGE_FILE) - return project.provider { - file.exists() ? file - : File.createTempFile(CARTHAGE_FILE, "") - } - } - - @InputFile - @Optional - @PathSensitive(PathSensitivity.RELATIVE) - Provider getCartResolvedFile() { - // Cf https://github.com/gradle/gradle/issues/2016 - File file = project.rootProject.file(CARTHAGE_FILE_RESOLVED) - return project.provider { - file.exists() ? file - : File.createTempFile(CARTHAGE_FILE_RESOLVED, "resolved") - } - } - - @Input - String getCarthagePlatformName() { - switch (project.xcodebuild.type) { - case Type.iOS: return CARTHAGE_PLATFORM_IOS - case Type.tvOS: return CARTHAGE_PLATFORM_TVOS - case Type.macOS: return CARTHAGE_PLATFORM_MACOS - case Type.watchOS: return CARTHAGE_PLATFORM_WATCHOS - default: return 'all' - } - } - - @OutputDirectory - Provider getOutputDirectory() { - return project.provider { - project.rootProject.file("Carthage/Build/" + getCarthagePlatformName()) - } + setDescription "Update and rebuild the Carthage project dependencies" } @TaskAction @@ -73,7 +16,9 @@ class CarthageUpdateTask extends AbstractXcodeTask { if (hasCartFile()) { logger.info('Update Carthage for platform ' + carthagePlatformName) - def output = services.get(StyledTextOutputFactory).create(CarthageUpdateTask) + def output = services.get(StyledTextOutputFactory) + .create(CarthageUpdateTask) + commandRunner.run( project.projectDir.absolutePath, [getCarthageCommand(), @@ -84,26 +29,4 @@ class CarthageUpdateTask extends AbstractXcodeTask { new ConsoleOutputAppender(output)) } } - - String getCarthageCommand() { - try { - return commandRunner.runWithResult("which", "carthage") - } catch (CommandRunnerException) { - // ignore, because try again with full path below - } - - try { - commandRunner.runWithResult("ls", CARTHAGE_USR_BIN_PATH) - return CARTHAGE_USR_BIN_PATH - } catch (CommandRunnerException) { - // ignore, because blow an exception is thrown - } - throw new IllegalStateException("The carthage command was not found. Make sure that Carthage is installed") - } - - boolean hasCartFile() { - return project.rootProject - .file(CARTHAGE_FILE) - .exists() - } } diff --git a/plugin/src/main/groovy/org/openbakery/coverage/CoverageTask.groovy b/plugin/src/main/groovy/org/openbakery/coverage/CoverageTask.groovy index 64b68eab..4c47646e 100644 --- a/plugin/src/main/groovy/org/openbakery/coverage/CoverageTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/coverage/CoverageTask.groovy @@ -4,6 +4,7 @@ import org.apache.commons.io.FilenameUtils import org.apache.commons.lang.StringUtils import org.gradle.api.tasks.TaskAction import org.openbakery.AbstractXcodeTask +import org.openbakery.XcodeBuildPluginExtension import org.openbakery.xcode.Destination class CoverageTask extends AbstractXcodeTask { @@ -48,17 +49,17 @@ class CoverageTask extends AbstractXcodeTask { def zipFilename = version + ".zip" def zip = new File(project.coverage.outputDirectory, zipFilename) def url = 'https://github.com/gcovr/gcovr/archive/' + zipFilename - ant.get(src: url, dest: project.coverage.outputDirectory, verbose:true) - ant.unzip(src: zip, dest:project.coverage.outputDirectory) + ant.get(src: url, dest: project.coverage.outputDirectory, verbose: true) + ant.unzip(src: zip, dest: project.coverage.outputDirectory) def gcovrCommand = new File(project.coverage.outputDirectory, 'gcovr-' + version + '/scripts/gcovr').absolutePath def commandList = [ - 'python', - gcovrCommand, - '-r', - '.' + 'python', + gcovrCommand, + '-r', + '.' ] String exclude = project.coverage.exclude @@ -100,9 +101,13 @@ class CoverageTask extends AbstractXcodeTask { for (Destination destination : project.coverage.testResultDestinations) { possibleDirectories.add("Build/ProfileData/" + destination.id + "/Coverage.profdata") } - + for (String directory : possibleDirectories) { - this.profileData = new File(project.xcodebuild.derivedDataPath, directory) + this.profileData = new File(project.extensions.findByType(XcodeBuildPluginExtension) + .derivedDataPath + .asFile + .getOrNull(), + directory) if (this.profileData.exists()) { return this.profileData } diff --git a/plugin/src/main/groovy/org/openbakery/crashlytics/CrashlyticsUploadTask.groovy b/plugin/src/main/groovy/org/openbakery/crashlytics/CrashlyticsUploadTask.groovy index 3b648510..2a750d29 100755 --- a/plugin/src/main/groovy/org/openbakery/crashlytics/CrashlyticsUploadTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/crashlytics/CrashlyticsUploadTask.groovy @@ -19,14 +19,14 @@ import org.gradle.api.tasks.TaskAction import org.gradle.internal.logging.text.StyledTextOutput import org.gradle.internal.logging.text.StyledTextOutputFactory import org.openbakery.AbstractDistributeTask -import org.openbakery.XcodePlugin import org.openbakery.output.ConsoleOutputAppender +import org.openbakery.packaging.PackageTask class CrashlyticsUploadTask extends AbstractDistributeTask { CrashlyticsUploadTask() { super() - dependsOn(XcodePlugin.PACKAGE_TASK_NAME) + dependsOn(PackageTask.NAME) this.description = "Upload the IPA to crashlytics for crash reports" } @@ -86,4 +86,4 @@ class CrashlyticsUploadTask extends AbstractDistributeTask { } -} \ No newline at end of file +} diff --git a/plugin/src/main/groovy/org/openbakery/extension/Signing.groovy b/plugin/src/main/groovy/org/openbakery/extension/Signing.groovy new file mode 100644 index 00000000..6d93319a --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/extension/Signing.groovy @@ -0,0 +1,163 @@ +package org.openbakery.extension + +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Internal +import org.openbakery.CommandRunner +import org.openbakery.codesign.CodesignParameters +import org.openbakery.signing.ProvisioningFile +import org.openbakery.signing.SigningMethod +import org.openbakery.util.PathHelper + +import javax.inject.Inject + +class Signing { + + final DirectoryProperty provisioningDestinationRoot = project.layout.directoryProperty() + final DirectoryProperty signingDestinationRoot = project.layout.directoryProperty() + final ListProperty mobileProvisionList = project.objects.listProperty(String) + final Property timeout = project.objects.property(Integer) + final Property signingMethod = project.objects.property(SigningMethod) + final Property certificateFriendlyName = project.objects.property(String) + final Property certificatePassword = project.objects.property(String) + final RegularFileProperty certificate = project.layout.fileProperty() + final Property certificateURI = project.objects.property(String) + final RegularFileProperty entitlementsFile = project.layout.fileProperty() + final RegularFileProperty keychain = project.layout.fileProperty() + final RegularFileProperty keyChainFile = project.layout.fileProperty() + + @Internal + final Provider> registeredProvisioningFiles = project.objects.listProperty(File) + + @Internal + final Provider> registeredProvisioning = project.objects.listProperty(ProvisioningFile) + + @Internal + final RegularFileProperty xcConfigFile = project.layout.fileProperty() + + @Deprecated + final Property> entitlementsMap = project.objects.property(Map) + + @Internal + Object keychainPathInternal + + String identity + String plugin + + public static final String KEYCHAIN_NAME_BASE = "gradle-" + + /** + * internal parameters + */ + private final Project project + private final CommandRunner commandRunner + + @Inject + Signing(Project project, + CommandRunner commandRunner) { + + this.commandRunner = commandRunner + this.project = project + this.signingDestinationRoot.set(project.layout.buildDirectory.dir("codesign")) + this.provisioningDestinationRoot.set(project.layout.buildDirectory.dir("provision")) + + this.keyChainFile.set(signingDestinationRoot.file(KEYCHAIN_NAME_BASE + + System.currentTimeMillis() + + ".keychain")) + this.timeout.set(3600) + + this.xcConfigFile.set(project.layout + .buildDirectory + .file(PathHelper.FOLDER_ARCHIVE + "/" + PathHelper.GENERATED_XCARCHIVE_FILE_NAME)) + } + + void setKeychain(Object keychain) { + if (keychain instanceof String && keychain.matches("^~/.*")) { + keychain = keychain.replaceFirst("~", System.getProperty('user.home')) + } + this.keychain.set(project.file(keychain)) + } + + public void setMethod(String method) { + signingMethod.set(SigningMethod.fromString(method) + .orElseThrow { + new IllegalArgumentException("Method : $method is not a valid export method") + }) + } + + File getKeychainPathInternal() { + return project.file(keychainPathInternal) + } + + void entitlements(Map entitlements) { + if (!this.entitlementsMap.present) { + this.entitlementsMap.set(entitlements) + } else { + this.entitlementsMap.get() << entitlements + } + } + + boolean hasEntitlementsFile() { + return entitlementsFile.present + } + + String getIdentity() { + return this.identity + } + + @Deprecated + void setEntitlementsFile(String value) { + entitlementsFile.set(new File(value)) + } + + void addMobileProvisionFile(File file) { + this.mobileProvisionList.add(file.toURI().toString()) + } + + @Deprecated + void setMobileProvisionURI(String value) { + this.mobileProvisionList.add(value) + } + + @Deprecated + void setMobileProvisionURI(String... values) { + this.mobileProvisionList.get().addAll(values.toList()) + } + + CodesignParameters getCodesignParameters() { + CodesignParameters result = new CodesignParameters() + result.signingIdentity = getIdentity() + if (registeredProvisioningFiles.present) { + result.mobileProvisionFiles = new ArrayList(registeredProvisioningFiles.get() + .asList() + .toArray() as ArrayList) + } + result.keychain = getKeychain().asFile.getOrNull() as File + result.signingIdentity = identity + result.entitlements = entitlementsMap.getOrNull() + result.entitlementsFile = entitlementsFile.asFile.getOrNull() + + return result + } + + @Override + public String toString() { + if (this.keychain != null) { + return "Signing{" + + " identity='" + identity + '\'' + + ", mobileProvisionURI='" + mobileProvisionList.get() + '\'' + + ", keychain='" + keychain + '\'' + + '}'; + } + return "Signing{" + + " identity='" + identity + '\'' + + ", certificateURI='" + certificate.getOrNull() + '\'' + + ", certificatePassword='" + certificatePassword.getOrNull() + '\'' + + ", mobileProvisionURI='" + mobileProvisionList.get() + '\'' + + '}'; + } +} diff --git a/plugin/src/main/groovy/org/openbakery/hockeyapp/HockeyAppUploadTask.groovy b/plugin/src/main/groovy/org/openbakery/hockeyapp/HockeyAppUploadTask.groovy index 6b9a887e..e15723d7 100644 --- a/plugin/src/main/groovy/org/openbakery/hockeyapp/HockeyAppUploadTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/hockeyapp/HockeyAppUploadTask.groovy @@ -15,31 +15,12 @@ */ package org.openbakery.hockeyapp -import org.apache.commons.io.FilenameUtils import org.apache.http.Consts -import org.apache.http.HttpEntity -import org.apache.http.HttpHost -import org.apache.http.HttpResponse -import org.apache.http.client.HttpClient -import org.apache.http.client.config.RequestConfig -import org.apache.http.client.methods.CloseableHttpResponse -import org.apache.http.client.methods.HttpPost import org.apache.http.entity.ContentType -import org.apache.http.entity.mime.MultipartEntity -import org.apache.http.entity.mime.MultipartEntityBuilder -import org.apache.http.entity.mime.content.FileBody -import org.apache.http.entity.mime.content.StringBody -import org.apache.http.impl.client.CloseableHttpClient -import org.apache.http.impl.client.DefaultHttpClient -import org.apache.http.impl.client.HttpClients -import org.apache.http.util.EntityUtils -import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction import org.openbakery.AbstractDistributeTask import org.openbakery.http.HttpUpload -import java.util.regex.Pattern - class HockeyAppUploadTask extends AbstractDistributeTask { @@ -130,7 +111,7 @@ class HockeyAppUploadTask extends AbstractDistributeTask { httpUpload.url = HOCKEY_APP_API_URL + project.hockeyapp.appID + "/provisioning_profiles" - if (project.xcodebuild.signing.mobileProvisionFile.size() != 1) { + if (project.xcodebuild.signing.registeredProvisioningFiles.get().size() != 1) { logger.debug("mobileProvisionFile not found"); return; } @@ -141,7 +122,7 @@ class HockeyAppUploadTask extends AbstractDistributeTask { } httpUpload.postRequest(getHttpHeaders(), - ["mobileprovision": project.xcodebuild.signing.mobileProvisionFile.get(0)] + ["mobileprovision": project.xcodebuild.signing.registeredProvisioningFiles.get().get(0)] ) } diff --git a/plugin/src/main/groovy/org/openbakery/oclint/OCLintTask.groovy b/plugin/src/main/groovy/org/openbakery/oclint/OCLintTask.groovy index c0d75c1f..6b725c4e 100644 --- a/plugin/src/main/groovy/org/openbakery/oclint/OCLintTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/oclint/OCLintTask.groovy @@ -3,6 +3,7 @@ package org.openbakery.oclint import org.apache.commons.io.FilenameUtils import org.gradle.api.tasks.TaskAction import org.openbakery.AbstractXcodeTask +import org.openbakery.util.SystemUtil class OCLintTask extends AbstractXcodeTask { @@ -37,7 +38,7 @@ class OCLintTask extends AbstractXcodeTask { def getDownloadURL() { - if (getOSVersion().minor >= 12) { + if (SystemUtil.getOsVersion().minor >= 12) { return "https://github.com/oclint/oclint/releases/download/v0.13/oclint-0.13-x86_64-darwin-17.0.0.tar.gz" } return "https://github.com/oclint/oclint/releases/download/v0.13/oclint-0.13-x86_64-darwin-16.7.0.tar.gz" diff --git a/plugin/src/main/groovy/org/openbakery/output/ConsoleOutputAppender.java b/plugin/src/main/groovy/org/openbakery/output/ConsoleOutputAppender.java index fa05b9a8..bcd1c5ff 100644 --- a/plugin/src/main/groovy/org/openbakery/output/ConsoleOutputAppender.java +++ b/plugin/src/main/groovy/org/openbakery/output/ConsoleOutputAppender.java @@ -15,7 +15,6 @@ public ConsoleOutputAppender(StyledTextOutput output) { @Override public void append(String line) { output.withStyle(StyledTextOutput.Style.Info).println(line); - } } diff --git a/plugin/src/main/groovy/org/openbakery/packaging/PackageLegacyTask.groovy b/plugin/src/main/groovy/org/openbakery/packaging/PackageLegacyTask.groovy new file mode 100644 index 00000000..2d78576c --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/packaging/PackageLegacyTask.groovy @@ -0,0 +1,333 @@ +package org.openbakery.packaging + +import org.apache.commons.io.FileUtils +import org.apache.commons.io.FilenameUtils +import org.gradle.api.Task +import org.gradle.api.specs.Spec +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.openbakery.AbstractDistributeTask +import org.openbakery.CommandRunnerException +import org.openbakery.bundle.ApplicationBundle +import org.openbakery.codesign.Codesign +import org.openbakery.codesign.CodesignParameters +import org.openbakery.codesign.ProvisioningProfileReader +import org.openbakery.signing.KeychainCreateTask +import org.openbakery.signing.ProvisioningInstallTask +import org.openbakery.util.PathHelper +import org.openbakery.xcode.Type + +class PackageLegacyTask extends AbstractDistributeTask { + + public static final String NAME = "packageLegacy" + + public static final String PACKAGE_PATH = "package" + File outputPath + + + private List appBundles + + String applicationBundleName + StyledTextOutput output + + + CodesignParameters codesignParameters = new CodesignParameters() + + PackageLegacyTask() { + super() + setDescription("Signs the app bundle that was created by the build and creates the ipa") + dependsOn( + KeychainCreateTask.TASK_NAME, + ProvisioningInstallTask.TASK_NAME + ) + + onlyIf(new Spec() { + @Override + boolean isSatisfiedBy(Task task) { + return getXcodeExtension().getType() == Type.watchOS || + getXcodeExtension().getType() == Type.macOS + } + }) + + output = services.get(StyledTextOutputFactory).create(PackageLegacyTask) + + } + + + @TaskAction + void packageApplication() throws IOException { + if (project.xcodebuild.isSimulatorBuildOf(Type.iOS) || project.xcodebuild.isSimulatorBuildOf(Type.tvOS)) { + logger.lifecycle("not a device build, so no codesign and packaging needed") + return + } + outputPath = PathHelper.resolvePackageFolder(project) + + File applicationFolder = createApplicationFolder() + + def applicationName = getApplicationNameFromArchive() + copy(getApplicationBundleDirectory(), applicationFolder) + + applicationBundleName = applicationName + ".app" + + File applicationPath = new File(applicationFolder, applicationBundleName) + + // copy onDemandResources + File onDemandResources = new File(getProductsDirectory(), "OnDemandResources") + if (onDemandResources.exists()) { + copy(onDemandResources, applicationPath) + } + + File bcSymbolsMaps = new File(getArchiveDirectory(), "BCSymbolMaps") + if (bcSymbolsMaps.exists()) { + copy(bcSymbolsMaps, applicationFolder.parentFile) + } + + enumerateExtensionSupportDirectories(getArchiveDirectory()) { File supportDirectory -> + copy(supportDirectory, applicationFolder.parentFile) + } + + ApplicationBundle applicationBundle = new ApplicationBundle(applicationPath, project.xcodebuild.type, project.xcodebuild.simulator) + appBundles = applicationBundle.getBundles() + + File resourceRules = new File(applicationFolder, applicationBundleName + "/ResourceRules.plist") + if (resourceRules.exists()) { + resourceRules.delete() + } + + + File infoPlist = getInfoPlistFile() + + try { + plistHelper.deleteValueFromPlist(infoPlist, "CFBundleResourceSpecification") + } catch (CommandRunnerException ex) { + // ignore, this means that the CFBundleResourceSpecification was not in the infoPlist + } + + def signSettingsAvailable = true + if (project.xcodebuild.signing.mobileProvisionFile == null) { + logger.warn('No mobile provision file provided.') + signSettingsAvailable = false; + } else if (!project.xcodebuild.signing.keychainPathInternal.exists()) { + logger.warn('No certificate or keychain found.') + signSettingsAvailable = false; + } + + codesignParameters.mergeMissing(project.xcodebuild.signing.codesignParameters) + codesignParameters.type = project.xcodebuild.type + codesignParameters.keychain = project.xcodebuild.signing.keychainPathInternal + Codesign codesign = new Codesign(xcode, codesignParameters, commandRunner, plistHelper) + + for (File bundle : appBundles) { + + if (isDeviceBuildiOStvOS()) { + removeFrameworkFromExtensions(bundle) + removeUnneededDylibsFromBundle(bundle) + embedProvisioningProfileToBundle(bundle) + } + + if (signSettingsAvailable) { + logger.info("Codesign app: {}", bundle) + codesign.sign(bundle) + } else { + String message = "Bundle not signed: " + bundle + output.withStyle(StyledTextOutput.Style.Failure).println(message) + } + } + + File appBundle = appBundles.last() + if (isDeviceBuildiOStvOS()) { + + boolean isAdHoc = isAdHoc(appBundle) + createIpa(applicationFolder, !isAdHoc) + } else { + createPackage(appBundle) + } + + } + + boolean isDeviceBuildiOStvOS() { + return project.xcodebuild.isDeviceBuildOf(Type.iOS) || project.xcodebuild.isDeviceBuildOf(Type.tvOS) + } + + boolean isAdHoc(File appBundle) { + File provisionFile = getProvisionFileForBundle(appBundle) + if (provisionFile == null) { + return false + } + ProvisioningProfileReader reader = new ProvisioningProfileReader(provisionFile, this.commandRunner, this.plistHelper) + return reader.isAdHoc() + } + + def removeFrameworkFromExtensions(File bundle) { + // appex extensions should not contain extensions + if (FilenameUtils.getExtension(bundle.toString()).equalsIgnoreCase("appex")) { + File frameworksPath = new File(bundle, "Frameworks") + if (frameworksPath.exists()) { + FileUtils.deleteDirectory(frameworksPath) + } + } + + } + + def removeUnneededDylibsFromBundle(File bundle) { + File libswiftRemoteMirror = new File(bundle, "libswiftRemoteMirror.dylib") + if (libswiftRemoteMirror.exists()) { + libswiftRemoteMirror.delete() + } + } + + File getProvisionFileForBundle(File bundle) { + String bundleIdentifier = getIdentifierForBundle(bundle) + return ProvisioningProfileReader.getProvisionFileForIdentifier(bundleIdentifier, project.xcodebuild.signing.mobileProvisionFile, this.commandRunner, this.plistHelper) + } + + + def addSwiftSupport(File payloadPath, String applicationBundleName) { + File frameworksPath = new File(payloadPath, applicationBundleName + "/Frameworks") + if (!frameworksPath.exists()) { + return null + } + + File swiftLibArchive = new File(getArchiveDirectory(), "SwiftSupport") + + if (swiftLibArchive.exists()) { + copy(swiftLibArchive, payloadPath.getParentFile()) + return new File(payloadPath.getParentFile(), "SwiftSupport") + } + return null + } + + + private void createZipPackage(File packagePath, String extension, boolean includeSwiftSupport) { + File packageBundle = new File(outputPath, getIpaFileName() + "." + extension) + if (!packageBundle.parentFile.exists()) { + packageBundle.parentFile.mkdirs() + } + + List filesToZip = [] + filesToZip << packagePath + + if (includeSwiftSupport) { + File swiftSupportPath = addSwiftSupport(packagePath, applicationBundleName) + if (swiftSupportPath != null) { + filesToZip << swiftSupportPath + } + } + + File bcSymbolMapsPath = new File(packagePath.getParentFile(), "BCSymbolMaps") + if (bcSymbolMapsPath.exists()) { + filesToZip << bcSymbolMapsPath + } + + enumerateExtensionSupportDirectories(packagePath.getParentFile()) { File supportDirectory -> + filesToZip << supportDirectory + } + + createZip(packageBundle, packagePath.getParentFile(), packagePath, *filesToZip) + } + + private void enumerateExtensionSupportDirectories(File parentDirectory, Closure closure) { + def directoryNames = ["MessagesApplicationExtensionSupport"] + + for (String name in directoryNames) { + File supportDirectory = new File(parentDirectory, name) + if (supportDirectory.exists()) { + closure(supportDirectory) + } + } + } + + private void createIpa(File payloadPath, boolean addSwiftSupport) { + createZipPackage(payloadPath, "ipa", addSwiftSupport) + } + + private void createPackage(File packagePath) { + + createZipPackage(packagePath, "zip", false) + } + + + private String getIdentifierForBundle(File bundle) { + File infoPlist + + if (isDeviceBuildiOStvOS()) { + infoPlist = new File(bundle, "Info.plist"); + } else { + infoPlist = new File(bundle, "Contents/Info.plist") + } + + String bundleIdentifier = plistHelper.getValueFromPlist(infoPlist, "CFBundleIdentifier") + return bundleIdentifier + } + + + private void embedProvisioningProfileToBundle(File bundle) { + File mobileProvisionFile = getProvisionFileForBundle(bundle) + if (mobileProvisionFile != null) { + File embeddedProvisionFile + + String profileExtension = FilenameUtils.getExtension(mobileProvisionFile.absolutePath) + embeddedProvisionFile = new File(getAppContentPath(bundle) + "embedded." + profileExtension) + + logger.info("provision profile - {}", embeddedProvisionFile) + + FileUtils.copyFile(mobileProvisionFile, embeddedProvisionFile) + } + } + + private File createSigningDestination(String name) throws IOException { + File destination = new File(outputPath, name) + if (destination.exists()) { + FileUtils.deleteDirectory(destination) + } + destination.mkdirs(); + return destination; + } + + private File createApplicationFolder() throws IOException { + + if (isDeviceBuildiOStvOS()) { + return createSigningDestination("Payload") + } else { + // same folder as signing + if (!outputPath.exists()) { + outputPath.mkdirs() + } + return outputPath + } + } + + private File getInfoPlistFile() { + return new File(getAppContentPath() + "Info.plist") + } + + private String getAppContentPath() { + + return getAppContentPath(appBundles.last()) + } + + private String getAppContentPath(File bundle) { + if (project.xcodebuild.type == Type.iOS || project.xcodebuild.type == Type.tvOS) { + return bundle.absolutePath + "/" + } + return bundle.absolutePath + "/Contents/" + } + + def getIpaFileName() { + if (project.xcodebuild.ipaFileName) { + return project.xcodebuild.ipaFileName + } else { + return getApplicationNameFromArchive() + } + } + + + String getSigningIdentity() { + return codesignParameters.signingIdentity + } + + void setSigningIdentity(String identity) { + codesignParameters.signingIdentity = identity + } +} diff --git a/plugin/src/main/groovy/org/openbakery/packaging/PackageTask.groovy b/plugin/src/main/groovy/org/openbakery/packaging/PackageTask.groovy index 3afbd362..a51a5df9 100644 --- a/plugin/src/main/groovy/org/openbakery/packaging/PackageTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/packaging/PackageTask.groovy @@ -1,322 +1,15 @@ package org.openbakery.packaging -import org.apache.commons.io.FileUtils -import org.apache.commons.io.FilenameUtils -import org.gradle.api.tasks.TaskAction -import org.gradle.internal.logging.text.StyledTextOutput -import org.gradle.internal.logging.text.StyledTextOutputFactory -import org.openbakery.AbstractDistributeTask -import org.openbakery.CommandRunnerException -import org.openbakery.bundle.ApplicationBundle -import org.openbakery.codesign.Codesign -import org.openbakery.codesign.CodesignParameters -import org.openbakery.xcode.Type -import org.openbakery.XcodePlugin -import org.openbakery.codesign.ProvisioningProfileReader +import org.gradle.api.DefaultTask -class PackageTask extends AbstractDistributeTask { +class PackageTask extends DefaultTask { - public static final String PACKAGE_PATH = "package" - File outputPath - - - private List appBundles - - String applicationBundleName - StyledTextOutput output - - - CodesignParameters codesignParameters = new CodesignParameters() + public static final String NAME = "package" PackageTask() { super() - setDescription("Signs the app bundle that was created by the build and creates the ipa") - dependsOn( - XcodePlugin.KEYCHAIN_CREATE_TASK_NAME, - XcodePlugin.PROVISIONING_INSTALL_TASK_NAME, - ) - finalizedBy( - XcodePlugin.KEYCHAIN_REMOVE_SEARCH_LIST_TASK_NAME - ) - - output = services.get(StyledTextOutputFactory).create(PackageTask) - - } - - - @TaskAction - void packageApplication() throws IOException { - if (project.xcodebuild.isSimulatorBuildOf(Type.iOS) || project.xcodebuild.isSimulatorBuildOf(Type.tvOS)) { - logger.lifecycle("not a device build, so no codesign and packaging needed") - return - } - outputPath = new File(project.getBuildDir(), PACKAGE_PATH) - - File applicationFolder = createApplicationFolder() - - def applicationName = getApplicationNameFromArchive() - copy(getApplicationBundleDirectory(), applicationFolder) - - applicationBundleName = applicationName + ".app" - - File applicationPath = new File(applicationFolder, applicationBundleName) - - // copy onDemandResources - File onDemandResources = new File(getProductsDirectory(), "OnDemandResources") - if (onDemandResources.exists()) { - copy(onDemandResources, applicationPath) - } - - File bcSymbolsMaps = new File(getArchiveDirectory(), "BCSymbolMaps") - if (bcSymbolsMaps.exists()) { - copy(bcSymbolsMaps, applicationFolder.parentFile) - } - - enumerateExtensionSupportDirectories(getArchiveDirectory()) { File supportDirectory -> - copy(supportDirectory, applicationFolder.parentFile) - } - - ApplicationBundle applicationBundle = new ApplicationBundle(applicationPath , project.xcodebuild.type, project.xcodebuild.simulator) - appBundles = applicationBundle.getBundles() - - File resourceRules = new File(applicationFolder, applicationBundleName + "/ResourceRules.plist") - if (resourceRules.exists()) { - resourceRules.delete() - } - - - File infoPlist = getInfoPlistFile() - - try { - plistHelper.deleteValueFromPlist(infoPlist, "CFBundleResourceSpecification") - } catch (CommandRunnerException ex) { - // ignore, this means that the CFBundleResourceSpecification was not in the infoPlist - } - - def signSettingsAvailable = true - if (project.xcodebuild.signing.mobileProvisionFile == null) { - logger.warn('No mobile provision file provided.') - signSettingsAvailable = false; - } else if (!project.xcodebuild.signing.keychainPathInternal.exists()) { - logger.warn('No certificate or keychain found.') - signSettingsAvailable = false; - } - - codesignParameters.mergeMissing(project.xcodebuild.signing.codesignParameters) - codesignParameters.type = project.xcodebuild.type - codesignParameters.keychain = project.xcodebuild.signing.keychainPathInternal - Codesign codesign = new Codesign(xcode, codesignParameters, commandRunner, plistHelper) - - for (File bundle : appBundles) { - - if (isDeviceBuildiOStvOS()) { - removeFrameworkFromExtensions(bundle) - removeUnneededDylibsFromBundle(bundle) - embedProvisioningProfileToBundle(bundle) - } - - if (signSettingsAvailable) { - logger.info("Codesign app: {}", bundle) - codesign.sign(bundle) - } else { - String message = "Bundle not signed: " + bundle - output.withStyle(StyledTextOutput.Style.Failure).println(message) - } - } - - File appBundle = appBundles.last() - if (isDeviceBuildiOStvOS()) { - - boolean isAdHoc = isAdHoc(appBundle) - createIpa(applicationFolder, !isAdHoc) - } else { - createPackage(appBundle) - } - - } - - boolean isDeviceBuildiOStvOS() { - return project.xcodebuild.isDeviceBuildOf(Type.iOS) || project.xcodebuild.isDeviceBuildOf(Type.tvOS) - } - - boolean isAdHoc(File appBundle) { - File provisionFile = getProvisionFileForBundle(appBundle) - if (provisionFile == null) { - return false - } - ProvisioningProfileReader reader = new ProvisioningProfileReader(provisionFile, this.commandRunner, this.plistHelper) - return reader.isAdHoc() - } - - def removeFrameworkFromExtensions(File bundle) { - // appex extensions should not contain extensions - if (FilenameUtils.getExtension(bundle.toString()).equalsIgnoreCase("appex")) { - File frameworksPath = new File(bundle, "Frameworks") - if (frameworksPath.exists()) { - FileUtils.deleteDirectory(frameworksPath) - } - } - - } - - def removeUnneededDylibsFromBundle(File bundle) { - File libswiftRemoteMirror = new File(bundle, "libswiftRemoteMirror.dylib") - if (libswiftRemoteMirror.exists()) { - libswiftRemoteMirror.delete() - } - } - - File getProvisionFileForBundle(File bundle) { - String bundleIdentifier = getIdentifierForBundle(bundle) - return ProvisioningProfileReader.getProvisionFileForIdentifier(bundleIdentifier, project.xcodebuild.signing.mobileProvisionFile, this.commandRunner, this.plistHelper) - } - - - def addSwiftSupport(File payloadPath, String applicationBundleName) { - File frameworksPath = new File(payloadPath, applicationBundleName + "/Frameworks") - if (!frameworksPath.exists()) { - return null - } - - File swiftLibArchive = new File(getArchiveDirectory(), "SwiftSupport") - - if (swiftLibArchive.exists()) { - copy(swiftLibArchive, payloadPath.getParentFile()) - return new File(payloadPath.getParentFile(), "SwiftSupport") - } - return null - } - - - private void createZipPackage(File packagePath, String extension, boolean includeSwiftSupport) { - File packageBundle = new File(outputPath, getIpaFileName() + "." + extension) - if (!packageBundle.parentFile.exists()) { - packageBundle.parentFile.mkdirs() - } - - List filesToZip = [] - filesToZip << packagePath - - if (includeSwiftSupport) { - File swiftSupportPath = addSwiftSupport(packagePath, applicationBundleName) - if (swiftSupportPath != null) { - filesToZip << swiftSupportPath - } - } - - File bcSymbolMapsPath = new File(packagePath.getParentFile(), "BCSymbolMaps") - if (bcSymbolMapsPath.exists()) { - filesToZip << bcSymbolMapsPath - } - - enumerateExtensionSupportDirectories(packagePath.getParentFile()) { File supportDirectory -> - filesToZip << supportDirectory - } - - createZip(packageBundle, packagePath.getParentFile(), packagePath, *filesToZip) - } - - private void enumerateExtensionSupportDirectories(File parentDirectory, Closure closure) { - def directoryNames = ["MessagesApplicationExtensionSupport"] - - for (String name in directoryNames) { - File supportDirectory = new File(parentDirectory, name) - if (supportDirectory.exists()) { - closure(supportDirectory) - } - } - } - - private void createIpa(File payloadPath, boolean addSwiftSupport) { - createZipPackage(payloadPath, "ipa", addSwiftSupport) - } - - private void createPackage(File packagePath) { - - createZipPackage(packagePath, "zip", false) - } - - - private String getIdentifierForBundle(File bundle) { - File infoPlist - - if (isDeviceBuildiOStvOS()) { - infoPlist = new File(bundle, "Info.plist"); - } else { - infoPlist = new File(bundle, "Contents/Info.plist") - } - - String bundleIdentifier = plistHelper.getValueFromPlist(infoPlist, "CFBundleIdentifier") - return bundleIdentifier - } - - - private void embedProvisioningProfileToBundle(File bundle) { - File mobileProvisionFile = getProvisionFileForBundle(bundle) - if (mobileProvisionFile != null) { - File embeddedProvisionFile - - String profileExtension = FilenameUtils.getExtension(mobileProvisionFile.absolutePath) - embeddedProvisionFile = new File(getAppContentPath(bundle) + "embedded." + profileExtension) - - logger.info("provision profile - {}", embeddedProvisionFile) - - FileUtils.copyFile(mobileProvisionFile, embeddedProvisionFile) - } - } - - private File createSigningDestination(String name) throws IOException { - File destination = new File(outputPath, name) - if (destination.exists()) { - FileUtils.deleteDirectory(destination) - } - destination.mkdirs(); - return destination; - } - - private File createApplicationFolder() throws IOException { - - if (isDeviceBuildiOStvOS()) { - return createSigningDestination("Payload") - } else { - // same folder as signing - if (!outputPath.exists()) { - outputPath.mkdirs() - } - return outputPath - } - } - - private File getInfoPlistFile() { - return new File(getAppContentPath() + "Info.plist") - } - - private String getAppContentPath() { - - return getAppContentPath(appBundles.last()) - } - - private String getAppContentPath(File bundle) { - if (project.xcodebuild.type == Type.iOS || project.xcodebuild.type == Type.tvOS) { - return bundle.absolutePath + "/" - } - return bundle.absolutePath + "/Contents/" - } - - def getIpaFileName() { - if (project.xcodebuild.ipaFileName) { - return project.xcodebuild.ipaFileName - } else { - return getApplicationNameFromArchive() - } - } - - - String getSigningIdentity() { - return codesignParameters.signingIdentity - } - void setSigningIdentity(String identity) { - codesignParameters.signingIdentity = identity + dependsOn(PackageLegacyTask.NAME) + dependsOn(PackageTaskIosAndTvOS.NAME) } } diff --git a/plugin/src/main/groovy/org/openbakery/packaging/PackageTaskIosAndTvOS.groovy b/plugin/src/main/groovy/org/openbakery/packaging/PackageTaskIosAndTvOS.groovy new file mode 100644 index 00000000..5f13fdc6 --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/packaging/PackageTaskIosAndTvOS.groovy @@ -0,0 +1,167 @@ +package org.openbakery.packaging + +import groovy.transform.CompileStatic +import groovy.transform.TypeCheckingMode +import org.gradle.api.DefaultTask +import org.gradle.api.Task +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* +import org.openbakery.CommandRunner +import org.openbakery.XcodePlugin +import org.openbakery.signing.KeychainCreateTask +import org.openbakery.signing.ProvisioningFile +import org.openbakery.signing.ProvisioningInstallTask +import org.openbakery.signing.SigningMethod +import org.openbakery.util.PathHelper +import org.openbakery.util.PlistHelper +import org.openbakery.xcode.Type +import org.openbakery.xcode.Xcodebuild + +@CompileStatic +class PackageTaskIosAndTvOS extends DefaultTask { + + @Input + public final Property signingMethod = project.objects.property(SigningMethod) + + @Input + final Provider scheme = project.objects.property(String) + + @Input + final Provider buildType = project.objects.property(Type) + + @Input + final ListProperty registeredProvisioningFiles = project.objects.listProperty(ProvisioningFile) + + @Input + final Property certificateFriendlyName = project.objects.property(String) + + @Input + final Provider bitCode = project.objects.property(Boolean) + + final Provider commandRunner = project.objects.property(CommandRunner) + final Provider plistHelper = project.objects.property(PlistHelper) + + public static final String DESCRIPTION = "Package the archive with Xcode-build" + public static final String NAME = "packageWithXcodeBuild" + + private static final String PLIST_KEY_METHOD = "method" + private static final String PLIST_KEY_SIGNING_STYLE = "signingStyle" + private static final String PLIST_KEY_COMPILE_BITCODE = "compileBitcode" + private static final String PLIST_KEY_PROVISIONING_PROFILE = "provisioningProfiles" + private static final String PLIST_KEY_SIGNING_CERTIFICATE = "signingCertificate" + private static final String PLIST_VALUE_SIGNING_METHOD_MANUAL = "manual" + private static final String FILE_EXPORT_OPTIONS_PLIST = "exportOptions.plist" + + PackageTaskIosAndTvOS() { + super() + + description = DESCRIPTION + + dependsOn(KeychainCreateTask.TASK_NAME) + dependsOn(ProvisioningInstallTask.TASK_NAME) + dependsOn(XcodePlugin.XCODE_CONFIG_TASK_NAME) + + onlyIf({ Task task -> + return buildType.get() == Type.iOS || + buildType.get() == Type.tvOS + }) + } + + @Input + @CompileStatic(TypeCheckingMode.SKIP) + Map getProvisioningMap() { + return registeredProvisioningFiles.get() + .collectEntries { [it.getApplicationIdentifier(), it.getName()] } + } + + @InputDirectory + File getArchiveFile() { + return PathHelper.resolveArchiveFile(project, scheme.get()) + } + + @OutputDirectory + File getOutputDirectory() { + File file = PathHelper.resolvePackageFolder(project) + file.mkdirs() + return file + } + + @OutputFile + File getExportOptionsPlistFile() { + return new File(project.buildDir, FILE_EXPORT_OPTIONS_PLIST) + } + + @TaskAction + private void packageArchive() { + logger.info("Packaging the archive") + + assert signingMethod.present: "Cannot package, the signing method is not defined" + assert getArchiveFile().exists() && getArchiveFile().isDirectory() + + generateExportOptionPlist() + packageIt() + } + + private boolean validateBitCodeSettings() { + Boolean bitCodeValue = bitCode.get() + SigningMethod method = signingMethod.get() + + if (method == SigningMethod.AppStore) { + if (buildType.get() == Type.tvOS) { + assert bitCodeValue: "Invalid configuration for the TvOS target " + + "`AppStore` upload requires BitCode enabled." + } + } else { + assert !bitCodeValue: "The BitCode setting (`xcodebuild.bitCode`) should be " + + "enabled only for the `AppStore` signing method" + } + + return bitCodeValue + } + + private void generateExportOptionPlist() { + File file = getExportOptionsPlistFile() + + plistHelper.get().create(file) + + // Signing method + addStringValueForPlist(PLIST_KEY_METHOD, + signingMethod.get().value) + + // Provisioning profiles map list + plistHelper.get().addDictForPlist(file, + PLIST_KEY_PROVISIONING_PROFILE, + getProvisioningMap()) + + // Certificate name + addStringValueForPlist(PLIST_KEY_SIGNING_CERTIFICATE, + certificateFriendlyName.get()) + + // BitCode + plistHelper.get().addValueForPlist(file, + PLIST_KEY_COMPILE_BITCODE, + validateBitCodeSettings()) + + // SigningMethod + addStringValueForPlist(PLIST_KEY_SIGNING_STYLE, + PLIST_VALUE_SIGNING_METHOD_MANUAL) + } + + private void addStringValueForPlist(String key, + String value) { + assert key != null + assert value != null + + plistHelper.get() + .addValueForPlist(getExportOptionsPlistFile(), key, value) + } + + private void packageIt() { + Xcodebuild.packageIpa(commandRunner.get(), + getArchiveFile(), + getOutputDirectory(), + getExportOptionsPlistFile()) + } +} diff --git a/plugin/src/main/groovy/org/openbakery/signing/AbstractKeychainTask.groovy b/plugin/src/main/groovy/org/openbakery/signing/AbstractKeychainTask.groovy deleted file mode 100644 index 506483bf..00000000 --- a/plugin/src/main/groovy/org/openbakery/signing/AbstractKeychainTask.groovy +++ /dev/null @@ -1,52 +0,0 @@ -package org.openbakery.signing - -import org.apache.commons.lang.StringUtils -import org.openbakery.AbstractXcodeTask -import org.openbakery.XcodeBuildPluginExtension -import org.openbakery.codesign.Security - -/** - * Created with IntelliJ IDEA. - * User: rene - * Date: 23.08.13 - * Time: 11:39 - * To change this template use File | Settings | File Templates. - */ -abstract class AbstractKeychainTask extends AbstractXcodeTask { - - Security security - - AbstractKeychainTask() { - security = new Security(commandRunner) - } - - - List getKeychainList() { - return security.getKeychainList() - } - - def setKeychainList(List keychainList) { - security.setKeychainList(keychainList) - } - - /** - * remove all gradle keychains from the keychain search list - * @return - */ - def removeGradleKeychainsFromSearchList() { - ListkeychainList = getKeychainList() - logger.debug("project.xcodebuild.signing.keychain should not be removed: {}", project.xcodebuild.signing.keychainPathInternal) - if (project.xcodebuild.signing.keychainPathInternal != null) { - keychainList.remove(project.xcodebuild.signing.keychainPathInternal) - } - setKeychainList(keychainList) - } - - def cleanupKeychain() { - project.xcodebuild.signing.signingDestinationRoot.deleteDir() - removeGradleKeychainsFromSearchList() - } - - - -} diff --git a/plugin/src/main/groovy/org/openbakery/signing/KeychainCleanupTask.groovy b/plugin/src/main/groovy/org/openbakery/signing/KeychainCleanupTask.groovy deleted file mode 100644 index 3077dcbd..00000000 --- a/plugin/src/main/groovy/org/openbakery/signing/KeychainCleanupTask.groovy +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2013 the original author or authors. - * - * 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 org.openbakery.signing - -import org.gradle.api.tasks.TaskAction -import org.openbakery.XcodeBuildPluginExtension - -class KeychainCleanupTask extends AbstractKeychainTask { - - KeychainCleanupTask() { - super() - this.description = "Cleanup the keychain" - } - - - - @TaskAction - def clean() { - if (project.xcodebuild.signing.keychain) { - logger.debug("Nothing to cleanup") - return - } - cleanupKeychain() - } - -} \ No newline at end of file diff --git a/plugin/src/main/groovy/org/openbakery/signing/KeychainCreateTask.groovy b/plugin/src/main/groovy/org/openbakery/signing/KeychainCreateTask.groovy index 01ae15a6..51d46b5b 100644 --- a/plugin/src/main/groovy/org/openbakery/signing/KeychainCreateTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/signing/KeychainCreateTask.groovy @@ -1,84 +1,232 @@ -/* - * Copyright 2013 the original author or authors. - * - * 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 org.openbakery.signing -import org.gradle.api.tasks.TaskAction -import org.gradle.api.InvalidUserDataException -import org.openbakery.xcode.Type -import org.openbakery.XcodePlugin +import de.undercouch.gradle.tasks.download.Download +import groovy.transform.CompileStatic +import org.apache.commons.io.FileUtils +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.openbakery.CommandRunner +import org.openbakery.CommandRunnerException +import org.openbakery.codesign.Security +import org.openbakery.util.FileUtil +import org.openbakery.util.SystemUtil +import org.openbakery.xcode.Version -class KeychainCreateTask extends AbstractKeychainTask { +import java.util.regex.Matcher +import java.util.regex.Pattern +@CompileStatic +class KeychainCreateTask extends Download { + + @InputFile + @Optional + final RegularFileProperty certificateFile = newInputFile() + + @Input + @Optional + final Property certificateUri = project.objects.property(String) + + @Input + final Property certificatePassword = project.objects.property(String) + + @Input + final Property keychainTimeout = project.objects.property(Integer) + + final Property certificateFriendlyName = project.objects.property(String) + + final Property commandRunnerProperty = project.objects.property(CommandRunner) + + @OutputFile + final RegularFileProperty keyChainFile = newOutputFile() + + final Property security = project.objects.property(Security) + final DirectoryProperty outputDirectory = newOutputDirectory() + + private static final Pattern PATTERN = ~/^\s{4}friendlyName:\s(?[^\n]+)/ + + static final String TASK_NAME = "keychainCreate" + static final String TASK_DESCRIPTION = "Create a keychain that is used for signing the app" + static final String KEYCHAIN_DEFAULT_PASSWORD = "This_is_the_default_keychain_password" + + private File temporaryCertificateFile KeychainCreateTask() { super() - this.description = "Create a keychain that is used for signing the app" + this.description = TASK_DESCRIPTION + onlyIf { + if (!certificateFile.present && !certificateUri.present) { + logger.warn("No signing certificate defined, will skip the keychain creation") + } + + if (!certificatePassword.present) { + logger.warn("No signing certificate password defined, will skip the keychain creation") + } + + if (keyChainFile.present && keyChainFile.asFile.get().exists()) { + logger.debug("Using keychain : " + keyChainFile.get()) + } + + return ((certificateFile.present || certificateUri.present) && + certificatePassword.present) || + (keyChainFile.present && keyChainFile.asFile.get().exists()) + } } @TaskAction - def create() { + void download() { + if (certificateUri.present) { + outputDirectory.get().asFile.mkdirs() + configureDownload() + super.download() + resolveCertificateFile() + } else { + File file = certificateFile.asFile.get() + temporaryCertificateFile = new File(outputDirectory.asFile.get(), file.name) + FileUtils.copyFile(file, temporaryCertificateFile) + } + + parseCertificateFile() + createTemporaryCertificateFile() + createKeyChainAndImportCertificate() + addKeyChainToThePartitionList() + setupOptionalTimeout() + project.gradle.buildFinished { + removeGradleKeychainsFromSearchList() + deleteTemporaryKeyChainFile() + } + } + private void configureDownload() { + this.src(certificateUri.get()) + this.dest(outputDirectory.get().asFile) + this.acceptAnyCertificate(true) + } - if (project.xcodebuild.signing.keychain) { - if (!project.xcodebuild.signing.keychain.exists()) { - throw new IllegalStateException("Keychain not found: " + project.xcodebuild.signing.keychain.absolutePath) + private void resolveCertificateFile() { + temporaryCertificateFile = getOutputFiles().first() + } + + private void parseCertificateFile() { + certificateFriendlyName.set(getSignatureFriendlyName()) + + // Delete on exit the downloaded files + project.gradle.buildFinished { + if (temporaryCertificateFile.exists()) { + temporaryCertificateFile.delete() } - logger.debug("Using keychain {}", project.xcodebuild.signing.keychain) - logger.debug("Internal keychain {}", project.xcodebuild.signing.keychainPathInternal) - return } + } - if (project.xcodebuild.signing.certificateURI == null) { - logger.debug("not certificateURI specifed so do not create the keychain"); - return + private void createTemporaryCertificateFile() { + temporaryCertificateFile = FileUtil.download(project, + outputDirectory.asFile.get(), + certificateUri.present + ? certificateUri.get() + : certificateFile.asFile.get().toURI().toString()) + + // Delete the temporary file on completion + project.gradle.buildFinished { + if (temporaryCertificateFile.exists()) { + temporaryCertificateFile.delete() + } } + } + + private void createKeyChainAndImportCertificate() { + security.get() + .createKeychain(keyChainFile.asFile.getOrNull(), + KEYCHAIN_DEFAULT_PASSWORD) + security.get() + .importCertificate(temporaryCertificateFile, + certificatePassword.get(), + keyChainFile.asFile.get()) + } - if (project.xcodebuild.signing.certificatePassword == null) { - throw new InvalidUserDataException("Property project.xcodebuild.signing.certificatePassword is missing") + private void addKeyChainToThePartitionList() { + Version systemVersion = SystemUtil.getOsVersion() + if (systemVersion.minor >= 9) { + List keychainList = getKeychainList() + keychainList.add(keyChainFile.asFile.get()) + populateKeyChain(keychainList) } - // first cleanup old keychain - cleanupKeychain() + if (systemVersion.minor >= 12) { + security.get().setPartitionList(keyChainFile.asFile.get(), + KEYCHAIN_DEFAULT_PASSWORD) + } + } - def certificateFile = download(project.xcodebuild.signing.signingDestinationRoot, project.xcodebuild.signing.certificateURI) + private void setupOptionalTimeout() { + if (keychainTimeout.present) { + security.get() + .setTimeout(keychainTimeout.get(), + keyChainFile.asFile.get()) + } + } - File keychain = project.xcodebuild.signing.keychainPathInternal + List getKeychainList() { + return security.get().getKeychainList() + } - security.createKeychain(keychain, project.xcodebuild.signing.keychainPassword) - security.importCertificate(new File(certificateFile), project.xcodebuild.signing.certificatePassword, keychain) + void populateKeyChain(List keychainList) { + security.get() + .setKeychainList(keychainList) + } + private void deleteTemporaryKeyChainFile() { + if (keyChainFile.asFile.get()) { + keyChainFile.asFile.get().delete() - if (getOSVersion().minor >= 9) { - List keychainList = getKeychainList() - keychainList.add(keychain) - setKeychainList(keychainList) + logger.info("The temporary keychain file has been deleted") } + } - if (getOSVersion().minor >= 12) { - security.setPartitionList(keychain, project.xcodebuild.signing.keychainPassword) - } + private void removeGradleKeychainsFromSearchList() { + if (keyChainFile.present) { + List list = getKeychainList() + list.removeIf { File file -> + return file.absolutePath == keyChainFile.get().asFile.absolutePath + } + populateKeyChain(list) - // Set a custom timeout on the keychain if requested - if (project.xcodebuild.signing.timeout != null) { - security.setTimeout(project.xcodebuild.signing.timeout, keychain) + logger.info("The temporary keychain has been removed from the search list") } } + String getSignatureFriendlyName() { + return java.util.Optional.ofNullable(getKeyContent(temporaryCertificateFile) + .split(System.getProperty("line.separator")) + .find { PATTERN.matcher(it).matches() }) + .map { PATTERN.matcher(it) } + .filter { Matcher it -> it.matches() } + .map { Matcher it -> + return it.group("friendlyName") + } + .orElseThrow { + new IllegalArgumentException("Failed to resolve the code signing identity from the certificate ") + } + } + private String getKeyContent(File file) { + String result + try { + result = commandRunnerProperty.get() + .runWithResult(["openssl", + "pkcs12", + "-nokeys", + "-in", + file.absolutePath, + "-passin", + "pass:" + certificatePassword.get()]) + } catch (CommandRunnerException exception) { + logger.warn(exception.toString()) + result = null + } -} \ No newline at end of file + return result + } +} diff --git a/plugin/src/main/groovy/org/openbakery/signing/KeychainRemoveFromSearchListTask.groovy b/plugin/src/main/groovy/org/openbakery/signing/KeychainRemoveFromSearchListTask.groovy deleted file mode 100644 index 02985e66..00000000 --- a/plugin/src/main/groovy/org/openbakery/signing/KeychainRemoveFromSearchListTask.groovy +++ /dev/null @@ -1,20 +0,0 @@ -package org.openbakery.signing - -import org.gradle.api.tasks.TaskAction -import org.openbakery.XcodePlugin - -class KeychainRemoveFromSearchListTask extends AbstractKeychainTask { - - KeychainRemoveFromSearchListTask() { - super() - mustRunAfter(XcodePlugin.CRASHLYTICS_TASK_NAME) - this.description = "Removes the gradle keychain from the search list" - } - - - - @TaskAction - def remove() { - removeGradleKeychainsFromSearchList() - } -} diff --git a/plugin/src/main/groovy/org/openbakery/signing/ProvisioningCleanupTask.groovy b/plugin/src/main/groovy/org/openbakery/signing/ProvisioningCleanupTask.groovy deleted file mode 100644 index e7023cc8..00000000 --- a/plugin/src/main/groovy/org/openbakery/signing/ProvisioningCleanupTask.groovy +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013 the original author or authors. - * - * 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 org.openbakery.signing - -import org.gradle.api.tasks.TaskAction -import org.openbakery.AbstractXcodeTask - - -class ProvisioningCleanupTask extends AbstractXcodeTask { - - ProvisioningCleanupTask() { - } - - @TaskAction - def clean() { - if (project.xcodebuild.signing.mobileProvisionDestinationRoot.exists()) { - logger.debug("deleting {}", project.xcodebuild.signing.mobileProvisionDestinationRoot) - project.xcodebuild.signing.mobileProvisionDestinationRoot.deleteDir() - - if (project.xcodebuild.signing.mobileProvisionDestinationRoot.exists()) { - logger.error("error deleting provisioning: {}", project.xcodebuild.signing.mobileProvisionDestinationRoot) - } - } else { - logger.debug("Provisioning destination cleanup skipped because the destination directory does not exit") - } - - File mobileprovisionPath = new File(System.getProperty("user.home") + "/Library/MobileDevice/Provisioning Profiles/") - - if (mobileprovisionPath.exists()) { - // find all the broken profile links that where created by this plugin - String profileLinksToDelete = commandRunner.runWithResult(["find", "-L", mobileprovisionPath.absolutePath, "-name", ProvisioningInstallTask.PROVISIONING_NAME_BASE +"*", "-type", "l"]); - String[] profiles = profileLinksToDelete.split("\n") - for (String profile : profiles) { - logger.debug("profile to delete {}", profile) - new File(profile).delete(); - } - } else { - logger.debug("nothing to cleanup") - } - } -} \ No newline at end of file diff --git a/plugin/src/main/groovy/org/openbakery/signing/ProvisioningFile.groovy b/plugin/src/main/groovy/org/openbakery/signing/ProvisioningFile.groovy new file mode 100644 index 00000000..ee686b80 --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/signing/ProvisioningFile.groovy @@ -0,0 +1,61 @@ +package org.openbakery.signing + +import org.apache.commons.io.FilenameUtils + +class ProvisioningFile implements Serializable { + + private File file + private String applicationIdentifier + private String uuid + private String teamIdentifier + private String teamName + private String name + + public static final String PROVISIONING_NAME_BASE = "gradle-" + + ProvisioningFile(File file, + String applicationIdentifier, + String uuid, + String teamIdentifier, + String teamName, + String name) { + this.applicationIdentifier = applicationIdentifier + this.file = file + this.uuid = uuid + this.teamIdentifier = teamIdentifier + this.teamName = teamName + this.name = name + } + + String getApplicationIdentifier() { + return applicationIdentifier + } + + File getFile() { + return file + } + + String getUuid() { + return uuid + } + + String getTeamIdentifier() { + return teamIdentifier + } + + String getTeamName() { + return teamName + } + + String getName() { + return name + } + + String getFormattedName() { + return formattedName(uuid, file) + } + + public static String formattedName(String uuid, File file) { + return PROVISIONING_NAME_BASE + uuid + "." + FilenameUtils.getExtension(file.getName()) + } +} diff --git a/plugin/src/main/groovy/org/openbakery/signing/ProvisioningInstallTask.groovy b/plugin/src/main/groovy/org/openbakery/signing/ProvisioningInstallTask.groovy index 4d1d3bf5..92d913fb 100644 --- a/plugin/src/main/groovy/org/openbakery/signing/ProvisioningInstallTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/signing/ProvisioningInstallTask.groovy @@ -1,88 +1,129 @@ -/* - * Copyright 2013 the original author or authors. - * - * 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 org.openbakery.signing +import de.undercouch.gradle.tasks.download.Download +import groovy.transform.CompileStatic +import org.apache.commons.io.FileUtils import org.apache.commons.io.FilenameUtils +import org.gradle.api.file.Directory +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.OutputFiles import org.gradle.api.tasks.TaskAction -import org.openbakery.AbstractXcodeTask +import org.openbakery.CommandRunner import org.openbakery.codesign.ProvisioningProfileReader -import org.openbakery.xcode.Type -import org.openbakery.XcodePlugin +import org.openbakery.util.PlistHelper -class ProvisioningInstallTask extends AbstractXcodeTask { +@CompileStatic +class ProvisioningInstallTask extends Download { - public final static PROVISIONING_NAME_BASE = "gradle-" + @Input + final Provider> mobileProvisioningList = project.objects.listProperty(String) + @OutputFiles + final ListProperty registeredProvisioning = project.objects.listProperty(File) + @OutputDirectory + final Provider outputDirectory = project.layout.directoryProperty() - ProvisioningInstallTask() { - super() - dependsOn(XcodePlugin.PROVISIONING_CLEAN_TASK_NAME) - this.description = "Installs the given provisioning profile" - } + final ListProperty registeredProvisioningFiles = project.objects.listProperty(ProvisioningFile) + final Property commandRunnerProperty = project.objects.property(CommandRunner) + final Property plistHelperProperty = project.objects.property(PlistHelper) - void linkToLibraray(File mobileProvisionFile) { - File provisionPath = new File(System.getProperty("user.home") + "/Library/MobileDevice/Provisioning Profiles/"); - if (!provisionPath.exists()) { - provisionPath.mkdirs() - } - - File mobileProvisionFileLinkToLibrary = new File(System.getProperty("user.home") + "/Library/MobileDevice/Provisioning Profiles/" + mobileProvisionFile.getName()); - if (mobileProvisionFileLinkToLibrary.exists()) { - mobileProvisionFileLinkToLibrary.delete() - } + public static final String TASK_NAME = "provisioningInstall" + public static final String TASK_DESCRIPTION = "Installs the given provisioning profile" + static final File PROVISIONING_DIR = new File(System.getProperty("user.home") + + "/Library/MobileDevice/Provisioning Profiles/") - commandRunner.run(["/bin/ln", "-s", mobileProvisionFile.absolutePath, mobileProvisionFileLinkToLibrary.absolutePath]) + ProvisioningInstallTask() { + super() + this.description = TASK_DESCRIPTION + this.onlyIf { !mobileProvisioningList.get().empty } } @TaskAction - def install() { - + @Override + void download() throws IOException { + outputDirectory.get().asFile.mkdirs() + configureDownload() + super.download() + postDownload() + } - if (project.xcodebuild.signing.mobileProvisionURI == null) { - logger.lifecycle("No provisioning profile specifed so do nothing here") - return - } + private void configureDownload() { + this.src(mobileProvisioningList.get().asList()) + this.dest(outputDirectory.get().asFile) + this.acceptAnyCertificate(true) + } - for (String mobileProvisionURI : project.xcodebuild.signing.mobileProvisionURI) { - def mobileProvisionFile = download(project.xcodebuild.signing.mobileProvisionDestinationRoot, mobileProvisionURI) + void registerProvisioning(ProvisioningFile provisioningFile) { + registeredProvisioning.add(provisioningFile.getFile()) + registeredProvisioningFiles.add(provisioningFile) + } + File registerProvisioningInToUserLibrary(ProvisioningFile provisioningFile) { + PROVISIONING_DIR.mkdirs() + File destinationFile = new File(PROVISIONING_DIR, provisioningFile.getFormattedName()) + if (destinationFile.exists()) { + destinationFile.delete() + } - ProvisioningProfileReader provisioningProfileIdReader = new ProvisioningProfileReader(new File(mobileProvisionFile), this.commandRunner, this.plistHelper) + FileUtils.copyFile(provisioningFile.getFile(), destinationFile) + return destinationFile + } - String uuid = provisioningProfileIdReader.getUUID() + private void postDownload() { + // For convenience we rename the mobile provisioning file in to a formatted name + List files = rename() + deleteFilesOnExit(files.collect { it.file }) + // Register it + files.each(this.®isterProvisioning) - String extension = FilenameUtils.getExtension(mobileProvisionFile) - String mobileProvisionName - mobileProvisionName = PROVISIONING_NAME_BASE + uuid + "." + extension + // Register into the user library + List registeredFiles = files.collect(this.®isterProvisioningInToUserLibrary) + deleteFilesOnExit(registeredFiles) + } + File fileFromPath(String path) { + return new File(outputDirectory.get().asFile, FilenameUtils.getName(path)) + } - File downloadedFile = new File(mobileProvisionFile) - File renamedProvisionFile = new File(downloadedFile.getParentFile(), mobileProvisionName) - downloadedFile.renameTo(renamedProvisionFile) + ProvisioningFile toProvisioningFile(File file) { + ProvisioningProfileReader reader = new ProvisioningProfileReader(file, + commandRunnerProperty.get(), + plistHelperProperty.get()) + + File renamedFile = new File(file.parentFile, + ProvisioningFile.formattedName(reader.getUUID(), file)) + file.renameTo(renamedFile) + + return new ProvisioningFile(renamedFile, + reader.getApplicationIdentifier(), + reader.getUUID(), + reader.getTeamIdentifierPrefix(), + reader.getTeamName(), + reader.getName()) + } - project.xcodebuild.signing.addMobileProvisionFile(renamedProvisionFile) + private List rename() { + return mobileProvisioningList.get() + .collect(this.&fileFromPath) + .collect(this.&toProvisioningFile) + } - linkToLibraray(renamedProvisionFile) + private void deleteFilesOnExit(final List files) { + project.gradle.buildFinished { + files.each { + logger.debug("Delete file : " + it.absolutePath) + it.delete() + } } - } -} \ No newline at end of file +} diff --git a/plugin/src/main/groovy/org/openbakery/signing/Signing.groovy b/plugin/src/main/groovy/org/openbakery/signing/Signing.groovy deleted file mode 100644 index 3bc87880..00000000 --- a/plugin/src/main/groovy/org/openbakery/signing/Signing.groovy +++ /dev/null @@ -1,172 +0,0 @@ -package org.openbakery.signing - -import org.gradle.api.Project -import org.openbakery.CommandRunner -import org.openbakery.codesign.CodesignParameters - -/** - * - * @author René Pirringer - * - */ -class Signing { - - public final static KEYCHAIN_NAME_BASE = "gradle-" - - - String identity - String certificateURI - String certificatePassword - List mobileProvisionURI = null - String keychainPassword = "This_is_the_default_keychain_password" - File keychain - Integer timeout = 3600 - String plugin - Object entitlementsFile - - Map entitlements - - /** - * internal parameters - */ - Object signingDestinationRoot - Object keychainPathInternal - final Project project - final String keychainName = KEYCHAIN_NAME_BASE + System.currentTimeMillis() + ".keychain" - CommandRunner commandRunner - - - Object mobileProvisionDestinationRoot - List mobileProvisionFile = new ArrayList() - - - - - public Signing(Project project) { - this.project = project; - this.commandRunner = new CommandRunner() - - this.signingDestinationRoot = { - return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve("codesign") - } - - this.keychainPathInternal = { - if (this.keychain != null) { - return this.keychain - } - return new File(this.signingDestinationRoot, keychainName) - } - - this.mobileProvisionDestinationRoot = { - return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve("provision") - } - - } - - void setKeychain(Object keychain) { - if (keychain instanceof String && keychain.matches("^~/.*")) { - keychain = keychain.replaceFirst("~", System.getProperty('user.home')) - } - this.keychain = project.file(keychain) - } - - - File getSigningDestinationRoot() { - return project.file(signingDestinationRoot) - } - - void setSigningDestinationRoot(Object keychainDestinationRoot) { - this.signingDestinationRoot = keychainDestinationRoot - } - - File getKeychainPathInternal() { - return project.file(keychainPathInternal) - } - - File getMobileProvisionDestinationRoot() { - return project.file(mobileProvisionDestinationRoot) - } - - void setMobileProvisionDestinationRoot(Object mobileProvisionDestinationRoot) { - this.mobileProvisionDestinationRoot = mobileProvisionDestinationRoot - } - - void setMobileProvisionURI(Object mobileProvisionURI) { - if (mobileProvisionURI instanceof List) { - this.mobileProvisionURI = mobileProvisionURI; - } else { - this.mobileProvisionURI = new ArrayList(); - this.mobileProvisionURI.add(mobileProvisionURI.toString()); - } - } - - - void addMobileProvisionFile(File mobileProvision) { - if (!mobileProvision.exists()) { - throw new IllegalArgumentException("given mobile provision file does not exist: " + mobileProvision.absolutePath) - } - mobileProvisionFile.add(mobileProvision) - } - - - File getEntitlementsFile() { - if (entitlementsFile != null) { - if (entitlementsFile instanceof File) { - return entitlementsFile - } - return project.file(entitlementsFile) - - } - return null - } - - boolean hasEntitlementsFile() { - return entitlementsFile != null && entitlementsFile.exists() - } - - void setEntitlementsFile(Object entitlementsFile) { - this.entitlementsFile = entitlementsFile - } - - String getIdentity() { - return this.identity - } - - - - public void entitlements(Map entitlements) { - this.entitlements = entitlements - - } - - @Override - public String toString() { - if (this.keychain != null) { - return "Signing{" + - " identity='" + identity + '\'' + - ", mobileProvisionURI='" + mobileProvisionURI + '\'' + - ", keychain='" + keychain + '\'' + - '}'; - } - return "Signing{" + - " identity='" + identity + '\'' + - ", certificateURI='" + certificateURI + '\'' + - ", certificatePassword='" + certificatePassword + '\'' + - ", mobileProvisionURI='" + mobileProvisionURI + '\'' + - '}'; - } - - CodesignParameters getCodesignParameters() { - CodesignParameters result = new CodesignParameters() - result.signingIdentity = getIdentity() - result.mobileProvisionFiles = getMobileProvisionFile().clone() - result.keychain = getKeychain() - if (entitlements != null) { - result.entitlements = entitlements.clone() - } - result.entitlementsFile = getEntitlementsFile() - return result - } - - -} diff --git a/plugin/src/main/groovy/org/openbakery/signing/SigningMethod.groovy b/plugin/src/main/groovy/org/openbakery/signing/SigningMethod.groovy new file mode 100644 index 00000000..307710f1 --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/signing/SigningMethod.groovy @@ -0,0 +1,25 @@ +package org.openbakery.signing + +import groovy.transform.CompileStatic + +@CompileStatic +enum SigningMethod { + AppStore("app-store"), + AdHoc("ad-hoc"), + Enterprise("enterprise"), + Dev("development") + + private final String value + + SigningMethod(String value) { + this.value = value + } + + String getValue() { + return value + } + + static Optional fromString(value) { + return Optional.ofNullable(values().find { it.getValue() == value }) + } +} diff --git a/plugin/src/main/groovy/org/openbakery/simulators/AbstractSimulatorTask.groovy b/plugin/src/main/groovy/org/openbakery/simulators/AbstractSimulatorTask.groovy index 6405f3fc..b16bf6a3 100644 --- a/plugin/src/main/groovy/org/openbakery/simulators/AbstractSimulatorTask.groovy +++ b/plugin/src/main/groovy/org/openbakery/simulators/AbstractSimulatorTask.groovy @@ -11,9 +11,10 @@ class AbstractSimulatorTask extends AbstractXcodeTask { Destination getDestination() { - return getDestinationResolver().getDestinations(project.xcodebuild.getXcodebuildParameters()).first() + return getDestinationResolver() + .getDestinations(project.xcodebuild.getXcodebuildParameters()) + .first() } - } diff --git a/plugin/src/main/groovy/org/openbakery/util/FileUtil.groovy b/plugin/src/main/groovy/org/openbakery/util/FileUtil.groovy new file mode 100644 index 00000000..dda3590a --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/util/FileUtil.groovy @@ -0,0 +1,37 @@ +package org.openbakery.util + +import org.apache.commons.io.FilenameUtils +import org.apache.commons.lang.StringUtils +import org.gradle.api.Project + +class FileUtil { + + static File download(Project project, + File toDirectory, + String address) throws IllegalArgumentException { + + if (StringUtils.isEmpty(address)) { + throw new IllegalArgumentException("Cannot download, because no address was given") + } + + if (!toDirectory.exists()) { + toDirectory.mkdirs() + } + + try { + project.ant.get(src: address, + dest: toDirectory.getPath(), + verbose: true) + } catch (Exception ex) { + project.logger.error("cannot download file from the given location: {}", address) + throw ex + } + + File file = new File(toDirectory, FilenameUtils.getName(address)) + return file + } + + static boolean isLocalFile(String uri) { + return new File(new URI(uri)).exists() + } +} diff --git a/plugin/src/main/groovy/org/openbakery/util/SystemUtil.groovy b/plugin/src/main/groovy/org/openbakery/util/SystemUtil.groovy new file mode 100644 index 00000000..7b142d39 --- /dev/null +++ b/plugin/src/main/groovy/org/openbakery/util/SystemUtil.groovy @@ -0,0 +1,43 @@ +package org.openbakery.util + +import org.openbakery.xcode.Version + +class SystemUtil { + static Version getOsVersion() { + Version result = new Version() + String versionString = System.getProperty("os.version") + Scanner scanner = new Scanner(versionString).useDelimiter("\\.") + if (scanner.hasNext()) { + result.major = scanner.nextInt() + } + if (scanner.hasNext()) { + result.minor = scanner.nextInt() + } + if (scanner.hasNext()) { + result.maintenance = scanner.nextInt() + } + return result + } + + public static boolean isValidUri(String value) { + boolean result = true + try { + new File(new URI(value)) + } catch (Exception exception) { + result = false + } + + return result + } + + public static boolean isValidUrl(String value) { + boolean result = true + try { + new File(new URL(value)) + } catch (Exception exception) { + result = false + } + + return result + } +} diff --git a/plugin/src/test/Resource/fake_distribution.p12 b/plugin/src/test/Resource/fake_distribution.p12 new file mode 100644 index 00000000..208b05e0 Binary files /dev/null and b/plugin/src/test/Resource/fake_distribution.p12 differ diff --git a/plugin/src/test/groovy/org/openbakery/AbstractDistributeTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/AbstractDistributeTaskSpecification.groovy index ca219b68..5ecbbefc 100644 --- a/plugin/src/test/groovy/org/openbakery/AbstractDistributeTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/AbstractDistributeTaskSpecification.groovy @@ -3,6 +3,7 @@ package org.openbakery import org.apache.tools.ant.util.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder +import org.openbakery.packaging.PackageLegacyTask import spock.lang.Specification class AbstractDistributeTaskSpecification extends Specification { @@ -23,7 +24,7 @@ class AbstractDistributeTaskSpecification extends Specification { project.apply plugin: org.openbakery.XcodePlugin - distributeTask = project.tasks.findByName(XcodePlugin.PACKAGE_TASK_NAME); + distributeTask = project.tasks.findByName(PackageLegacyTask.NAME) //XcodeProjectFile xcodeProjectFile = new XcodeProjectFile(project, new File(projectDir, "ExampleWatchkit.xcodeproj/project.pbxproj")); diff --git a/plugin/src/test/groovy/org/openbakery/AbstractXcodeBuildTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/AbstractXcodeBuildTaskSpecification.groovy index fb6e3170..c82a991a 100644 --- a/plugin/src/test/groovy/org/openbakery/AbstractXcodeBuildTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/AbstractXcodeBuildTaskSpecification.groovy @@ -8,27 +8,27 @@ import spock.lang.Specification class AbstractXcodeBuildTaskSpecification extends Specification { - Project project AbstractXcodeBuildTask xcodeBuildTask File projectDir - - + XcodeBuildPluginExtension extension def setup() { projectDir = new File(System.getProperty("java.io.tmpdir"), "gradle-xcodebuild") project = ProjectBuilder.builder().withProjectDir(projectDir).build() project.buildDir = new File('build').absoluteFile - project.apply plugin: org.openbakery.XcodePlugin + project.apply plugin: XcodePlugin xcodeBuildTask = project.getTasks().getByPath(XcodePlugin.XCODE_BUILD_TASK_NAME) + + extension = project.extensions + .getByType(XcodeBuildPluginExtension) } def cleanup() { - FileUtils.deleteDirectory(project.projectDir) + FileUtils.deleteDirectory(project.projectDir) } - def "test get identity when is set manually"() { when: project.xcodebuild.signing.identity = "my identity" @@ -37,7 +37,6 @@ class AbstractXcodeBuildTaskSpecification extends Specification { xcodeBuildTask.getSigningIdentity() == "my identity" } - def "security is initialized"() { expect: xcodeBuildTask.security != null @@ -48,12 +47,12 @@ class AbstractXcodeBuildTaskSpecification extends Specification { given: File keychain = new File(projectDir, "my.keychain") FileUtils.writeStringToFile(keychain, "dummy") - project.xcodebuild.signing.keychain = keychain - project.xcodebuild.signing.identity = null + extension.signing.keychain = keychain + extension.signing.identity = null when: Security security = Mock(Security) - security.getIdentity(project.xcodebuild.signing.getKeychainPathInternal()) >> "my identity from security" + security.getIdentity(extension.signing.getKeychainPathInternal()) >> "my identity from security" xcodeBuildTask.security = security then: @@ -65,11 +64,10 @@ class AbstractXcodeBuildTaskSpecification extends Specification { def "test get identity null and keychain does not exist should return null"() { when: - project.xcodebuild.signing.identity = null - project.xcodebuild.signing.keychain = new File("my.keychain") + extension.signing.identity = null + extension.signing.keychain = new File("my.keychain") then: xcodeBuildTask.getSigningIdentity() == null } - } diff --git a/plugin/src/test/groovy/org/openbakery/InfoPlistModifyTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/InfoPlistModifyTaskSpecification.groovy index 0e5d4df0..a580c9ae 100644 --- a/plugin/src/test/groovy/org/openbakery/InfoPlistModifyTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/InfoPlistModifyTaskSpecification.groovy @@ -5,8 +5,9 @@ import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.testdouble.PlistHelperStub import spock.lang.Specification +import spock.lang.Unroll -class InfoPlistModifyTaskSpecification extends Specification{ +class InfoPlistModifyTaskSpecification extends Specification { Project project @@ -18,15 +19,12 @@ class InfoPlistModifyTaskSpecification extends Specification{ CommandRunner commandRunner = Mock(CommandRunner) def setup() { - projectDir = new File(System.getProperty("java.io.tmpdir"), "gradle-xcodebuild") project = ProjectBuilder.builder().withProjectDir(projectDir).build() project.apply plugin: org.openbakery.XcodePlugin - projectDir.mkdirs() - project.xcodebuild.infoPlist = "App-Info.plist" task = project.tasks.findByName('infoplistModify') @@ -35,14 +33,57 @@ class InfoPlistModifyTaskSpecification extends Specification{ infoPlist = new File(task.project.projectDir, "App-Info.plist") FileUtils.writeStringToFile(infoPlist, "dummy") - - } def cleanup() { FileUtils.deleteDirectory(project.projectDir) } + def "add suffix"() { + given: + project.infoplist.bundleIdentifier = 'org.openbakery.test.Example' + project.infoplist.bundleIdentifierSuffix = '.suffix' + + when: + task.prepare() + + then: + plistHelper.getValueFromPlist(infoPlist, "CFBundleIdentifier") == 'org.openbakery.test.Example.suffix' + } + + @Unroll + def "add suffix `#bundleId` suffix : `#suffix`"() { + given: + BuildConfiguration bcRelease = new BuildConfiguration("Target") + bcRelease.bundleIdentifier = bundleId + + BuildTargetConfiguration btc = new BuildTargetConfiguration() + btc.buildSettings[configuration] = bcRelease + + HashMap projectSettings = new HashMap<>() + projectSettings.put(scheme, btc) + + def extension = project.extensions.getByType(XcodeBuildPluginExtension) + extension.projectSettings = projectSettings + extension.scheme.set(scheme) + extension.configuration = configuration + + project.infoplist.bundleIdentifierSuffix = suffix + + when: + task.prepare() + + then: + noExceptionThrown() + plistHelper.getValueFromPlist(infoPlist, "CFBundleIdentifier") == expectedResult + + where: + configuration | scheme | suffix | bundleId | expectedResult + "Release" | "Test" | ".suffix" | "he.llo.world" | "he.llo.world.suffix" + "Release" | "Test" | "" | "he.llo.world" | "he.llo.world" + "Release" | "Test" | null | "he.llo.world" | null + "Debug" | "Test" | null | "he.llo.world" | null + } def "modify BundleIdentifier"() { given: @@ -65,11 +106,10 @@ class InfoPlistModifyTaskSpecification extends Specification{ then: plistHelper.plistCommands[0] == "Add CFBundleURLTypes:0:CFBundleURLName string" - } def "modify command multiple"() { - project.infoplist.commands = ["Add CFBundleURLTypes:0:CFBundleURLName string", "Add CFBundleURLTypes:0:CFBundleURLSchemes array" ] + project.infoplist.commands = ["Add CFBundleURLTypes:0:CFBundleURLName string", "Add CFBundleURLTypes:0:CFBundleURLSchemes array"] when: task.prepare() @@ -100,7 +140,6 @@ class InfoPlistModifyTaskSpecification extends Specification{ then: plistHelper.getValueFromPlist(infoPlist, "CFBundleShortVersionString") == "1.2.3" - } def "nothing to modify"() { diff --git a/plugin/src/test/groovy/org/openbakery/PrepareXcodeArchivingTaskTest.groovy b/plugin/src/test/groovy/org/openbakery/PrepareXcodeArchivingTaskTest.groovy new file mode 100644 index 00000000..8efa86c0 --- /dev/null +++ b/plugin/src/test/groovy/org/openbakery/PrepareXcodeArchivingTaskTest.groovy @@ -0,0 +1,96 @@ +package org.openbakery + +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import org.openbakery.codesign.ProvisioningProfileReader +import org.openbakery.extension.Signing +import org.openbakery.util.PlistHelper +import spock.lang.Specification + +import static org.openbakery.PrepareXcodeArchivingTask.* + +class PrepareXcodeArchivingTaskTest extends Specification { + + PrepareXcodeArchivingTask subject + Signing signing + Project project + File outputFile + File entitlementsFile + + CommandRunner commandRunner = Mock(CommandRunner) + ProvisioningProfileReader provisioningProfileReader = Mock(ProvisioningProfileReader) + PlistHelper plistHelper = Mock(PlistHelper) + + private static final String FAKE_TEAM_ID = "Team Identifier" + private static final String FAKE_BUNDLE_IDENTIFIER = "co.test.test" + private static final String FAKE_FRIENDLY_NAME = "Fake Certificate FriendlyName (12345)" + private static final String FAKE_UUID = "FAKE_UUID" + private static final String FAKE_PROV_NAME = "Provisioning Name" + + @Rule + final TemporaryFolder tmpDirectory = new TemporaryFolder() + + def setup() { + this.project = ProjectBuilder.builder().withProjectDir(tmpDirectory.root).build() + this.project.apply plugin: XcodePlugin + + this.entitlementsFile = tmpDirectory.newFile("test.entitlements") + this.outputFile = tmpDirectory.newFile("test.xcconfig") + this.signing = project.extensions.findByType(Signing) + + configureSubject() + + provisioningProfileReader.getTeamIdentifierPrefix() >> FAKE_TEAM_ID + provisioningProfileReader.getUUID() >> FAKE_UUID + provisioningProfileReader.getName() >> FAKE_PROV_NAME + } + + def configureSubject() { + subject = project.tasks.findByName(NAME) as PrepareXcodeArchivingTask + subject.certificateFriendlyName.set(FAKE_FRIENDLY_NAME) + subject.commandRunnerProperty.set(commandRunner) + subject.configurationBundleIdentifier.set(FAKE_BUNDLE_IDENTIFIER) + subject.outputFile.set(outputFile) + subject.plistHelperProperty.set(plistHelper) + subject.provisioningReader.set(provisioningProfileReader) + } + + def "The generation should be executed without exception"() { + when: + subject.generate() + + then: + noExceptionThrown() + + and: "The generate file content should be valid" + String text = outputFile.text + text.contains("${KEY_CODE_SIGN_IDENTITY} = ${FAKE_FRIENDLY_NAME}") + text.contains("${KEY_DEVELOPMENT_TEAM} = ${FAKE_TEAM_ID}") + text.contains("${KEY_PROVISIONING_PROFILE_ID} = ${FAKE_UUID}") + text.contains("${KEY_PROVISIONING_PROFILE_SPEC} = ${FAKE_PROV_NAME}") + + and: "And no entitlements information should be present" + !text.contains("${KEY_CODE_SIGN_ENTITLEMENTS} = ") + } + + def "The generate file should refer the entitlements file is present"() { + when: + subject.entitlementsFile.set(entitlementsFile) + subject.generate() + + then: + noExceptionThrown() + + and: + String text = outputFile.text + text.contains("${KEY_CODE_SIGN_IDENTITY} = ${FAKE_FRIENDLY_NAME}") + text.contains("${KEY_DEVELOPMENT_TEAM} = ${FAKE_TEAM_ID}") + text.contains("${KEY_PROVISIONING_PROFILE_ID} = ${FAKE_UUID}") + text.contains("${KEY_PROVISIONING_PROFILE_SPEC} = ${FAKE_PROV_NAME}") + + and: "No entitlements information should be present" + text.contains("${KEY_CODE_SIGN_ENTITLEMENTS} = ${entitlementsFile.absolutePath}") + } +} diff --git a/plugin/src/test/groovy/org/openbakery/XcodeBuildArchiveTaskOSXSpecification.groovy b/plugin/src/test/groovy/org/openbakery/XcodeBuildArchiveTaskOSXSpecification.groovy index 961ae033..4fd8d4b2 100644 --- a/plugin/src/test/groovy/org/openbakery/XcodeBuildArchiveTaskOSXSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/XcodeBuildArchiveTaskOSXSpecification.groovy @@ -4,6 +4,7 @@ import org.apache.commons.configuration.plist.XMLPropertyListConfiguration import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder +import org.openbakery.archiving.XcodeBuildLegacyArchiveTask import org.openbakery.testdouble.PlistHelperStub import org.openbakery.xcode.Type import spock.lang.Specification @@ -12,7 +13,7 @@ class XcodeBuildArchiveTaskOSXSpecification extends Specification { Project project - XcodeBuildArchiveTask xcodeBuildArchiveTask; + XcodeBuildLegacyArchiveTask xcodeBuildArchiveTask; File projectDir File buildOutputDirectory @@ -32,7 +33,7 @@ class XcodeBuildArchiveTaskOSXSpecification extends Specification { project.xcodebuild.signing.keychain = "/var/tmp/gradle.keychain" project.xcodebuild.signing.identity = "my identity" - xcodeBuildArchiveTask = project.getTasks().getByPath(XcodePlugin.ARCHIVE_TASK_NAME) + xcodeBuildArchiveTask = project.getTasks().getByPath(XcodeBuildLegacyArchiveTask.NAME) xcodeBuildArchiveTask.commandRunner = commandRunner xcodeBuildArchiveTask.parameters.type = Type.macOS diff --git a/plugin/src/test/groovy/org/openbakery/XcodeBuildArchiveTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/XcodeBuildArchiveTaskSpecification.groovy index f84d1752..76142e4e 100644 --- a/plugin/src/test/groovy/org/openbakery/XcodeBuildArchiveTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/XcodeBuildArchiveTaskSpecification.groovy @@ -5,6 +5,8 @@ import org.apache.commons.io.FileUtils import org.apache.commons.lang.RandomStringUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder +import org.openbakery.archiving.XcodeBuildLegacyArchiveTask +import org.openbakery.signing.ProvisioningInstallTask import org.openbakery.testdouble.PlistHelperStub import org.openbakery.testdouble.SimulatorControlStub import org.openbakery.testdouble.XcodeFake @@ -23,7 +25,7 @@ class XcodeBuildArchiveTaskSpecification extends Specification { Project project - XcodeBuildArchiveTask xcodeBuildArchiveTask + XcodeBuildLegacyArchiveTask xcodeBuildArchiveTask File projectDir File buildOutputDirectory @@ -33,7 +35,6 @@ class XcodeBuildArchiveTaskSpecification extends Specification { PlistHelperStub plistHelper = new PlistHelperStub() def setup() { - String tmpName = "gradle-xcodebuild-" + RandomStringUtils.randomAlphanumeric(5) projectDir = new File(System.getProperty("java.io.tmpdir"), tmpName) project = ProjectBuilder.builder().withProjectDir(projectDir).build() @@ -48,7 +49,7 @@ class XcodeBuildArchiveTaskSpecification extends Specification { project.xcodebuild.signing.identity = "my identity" - xcodeBuildArchiveTask = project.getTasks().getByPath(XcodePlugin.ARCHIVE_TASK_NAME) + xcodeBuildArchiveTask = project.getTasks().getByPath(XcodeBuildLegacyArchiveTask.NAME) xcodeBuildArchiveTask.plistHelper = plistHelper xcodeBuildArchiveTask.commandRunner = commandRunner xcodeBuildArchiveTask.xcode.commandRunner = commandRunner @@ -159,7 +160,7 @@ class XcodeBuildArchiveTaskSpecification extends Specification { dependsOn.size() == 2 dependsOn.contains(XcodePlugin.XCODE_BUILD_TASK_NAME) - dependsOn.contains(XcodePlugin.PROVISIONING_INSTALL_TASK_NAME) + dependsOn.contains(ProvisioningInstallTask.TASK_NAME) } diff --git a/plugin/src/test/groovy/org/openbakery/XcodeBuildPluginExtensionSpecification.groovy b/plugin/src/test/groovy/org/openbakery/XcodeBuildPluginExtensionSpecification.groovy index ae36f058..8e2bbb09 100644 --- a/plugin/src/test/groovy/org/openbakery/XcodeBuildPluginExtensionSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/XcodeBuildPluginExtensionSpecification.groovy @@ -29,8 +29,7 @@ class XcodeBuildPluginExtensionSpecification extends Specification { project = ProjectBuilder.builder().withProjectDir(projectDir).build() project.apply plugin: org.openbakery.XcodePlugin - extension = new XcodeBuildPluginExtension(project) - extension.commandRunner = commandRunner + extension = new XcodeBuildPluginExtension(project, commandRunner) extension.infoPlist = "Info.plist"; @@ -154,7 +153,7 @@ class XcodeBuildPluginExtensionSpecification extends Specification { when: File projectDir = new File("../example/iOS/Example") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) XcodeProjectFile xcodeProjectFile = new XcodeProjectFile(project, new File(projectDir, "Example.xcodeproj/project.pbxproj")) extension.projectSettings = xcodeProjectFile.getProjectSettings() extension.type = Type.iOS @@ -176,8 +175,8 @@ class XcodeBuildPluginExtensionSpecification extends Specification { when: File projectDir = new File("../example/iOS/Example") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) - extension.commandRunner = new CommandRunner() + extension = new XcodeBuildPluginExtension(project, commandRunner) + XcodeProjectFile xcodeProjectFile = new XcodeProjectFile(project, new File(projectDir, "Example.xcodeproj/project.pbxproj")) extension.projectSettings = xcodeProjectFile.getProjectSettings() @@ -198,8 +197,8 @@ class XcodeBuildPluginExtensionSpecification extends Specification { when: File projectDir = new File("../example/tvOS/Example_tvOS_Swift") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) - extension.commandRunner = new CommandRunner() + extension = new XcodeBuildPluginExtension(project, commandRunner) + XcodeProjectFile xcodeProjectFile = new XcodeProjectFile(project, new File(projectDir, "Example_tvOS_Swift.xcodeproj/project.pbxproj")) extension.projectSettings = xcodeProjectFile.getProjectSettings() @@ -216,12 +215,11 @@ class XcodeBuildPluginExtensionSpecification extends Specification { } - def "application bundle for watchos"() { - when: - + def "application bundle for ios"() { + setup: File projectDir = new File("../example/iOS/ExampleWatchkit") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) extension.projectSettings = createProjectSettings() extension.type = Type.iOS extension.target = "ExampleWatchkit WatchKit App" @@ -230,21 +228,20 @@ class XcodeBuildPluginExtensionSpecification extends Specification { extension.productType = "app" extension.infoPlist = "../../example/iOS/ExampleWatchkit/ExampleWatchkit WatchKit App/Info.plist" - - String applicationBundle = extension.getApplicationBundle().absolutePath; + when: + String applicationBundle = extension.getApplicationBundle().absolutePath then: - applicationBundle.endsWith("build/sym/Debug-iphoneos/ExampleWatchkit.app") - + applicationBundle.endsWith("build/sym/Debug-iphoneos/ExampleWatchkit WatchKit App.app") } def "application bundle for watch find parent"() { when: File projectDir = new File("../example/iOS/ExampleWatchkit") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) extension.projectSettings = createProjectSettings() - extension.type = Type.iOS + extension.type = Type.watchOS extension.target = "ExampleWatchkit WatchKit App" extension.simulator = false @@ -252,11 +249,9 @@ class XcodeBuildPluginExtensionSpecification extends Specification { BuildConfiguration parent = extension.getParent(extension.projectSettings["ExampleWatchkit WatchKit App"].buildSettings["Debug"]) then: - parent.bundleIdentifier == "org.openbakery.test.Example" + parent.bundleIdentifier == "org.openbakery.test.Example.watchkitapp" } - - void mockValueFromPlist(String key, String value) { PlistHelperStub plistHelperStub = new PlistHelperStub() File infoPlist = new File(project.projectDir, "Info.plist") @@ -418,7 +413,7 @@ class XcodeBuildPluginExtensionSpecification extends Specification { when: File projectDir = new File("../example/iOS/Example") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) XcodeProjectFile xcodeProjectFile = new XcodeProjectFile(project, new File(projectDir, "Example.xcodeproj/project.pbxproj")) extension.projectSettings = xcodeProjectFile.getProjectSettings() extension.type = Type.iOS @@ -436,7 +431,7 @@ class XcodeBuildPluginExtensionSpecification extends Specification { when: File projectDir = new File("../example/tvOS/Example_tvOS_Swift") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) XcodeProjectFile xcodeProjectFile = new XcodeProjectFile(project, new File(projectDir, "Example_tvOS_Swift.xcodeproj/project.pbxproj")) extension.projectSettings = xcodeProjectFile.getProjectSettings() extension.type = Type.tvOS @@ -454,7 +449,7 @@ class XcodeBuildPluginExtensionSpecification extends Specification { when: File projectDir = new File("../example/OSX/ExampleOSX") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) XcodeProjectFile xcodeProjectFile = new XcodeProjectFile(project, new File(projectDir, "ExampleOSX.xcodeproj/project.pbxproj")) extension.projectSettings = xcodeProjectFile.getProjectSettings() extension.type = Type.macOS @@ -472,7 +467,7 @@ class XcodeBuildPluginExtensionSpecification extends Specification { when: File projectDir = new File("../example/iOS/Example") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) then: extension.projectFile.canonicalFile == new File("../example/iOS/Example/Example.xcodeproj").canonicalFile @@ -483,7 +478,7 @@ class XcodeBuildPluginExtensionSpecification extends Specification { setup: File projectDir = new File(".") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) when: File missingFile = extension.projectFile @@ -497,64 +492,19 @@ class XcodeBuildPluginExtensionSpecification extends Specification { when: File projectDir = new File("../example/iOS") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) extension.projectFile = "Example/Example.xcodeproj" then: extension.projectFile.canonicalFile == new File("../example/iOS/Example/Example.xcodeproj").canonicalFile } - - def "XcodebuildParameters are created with proper values"() { - when: - File projectDir = new File("../example/macOS/ExampleOSX") - project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) - extension.type = Type.macOS - extension.simulator = false - extension.target = "ExampleOSX" - extension.scheme = "ExampleScheme" - - extension.workspace = "workspace" - extension.configuration = "configuration" - extension.additionalParameters = "additionalParameters" - - extension.dstRoot = new File(projectDir, "dstRoot") - extension.objRoot = new File(projectDir, "objRoot") - extension.symRoot = new File(projectDir, "symRoot") - extension.sharedPrecompsDir = new File(projectDir, "sharedPrecompsDir") - extension.derivedDataPath = new File(projectDir, "derivedDataPath") - extension.arch = ['i386'] - - - def parameters = extension.getXcodebuildParameters() - - then: - parameters.type == Type.macOS - parameters.simulator == false - parameters.target == "ExampleOSX" - parameters.scheme == "ExampleScheme" - parameters.workspace == "workspace" - parameters.configuration == "configuration" - parameters.additionalParameters == "additionalParameters" - parameters.dstRoot.canonicalPath == new File(projectDir, "dstRoot").canonicalPath - parameters.objRoot.canonicalPath == new File(projectDir, "objRoot").canonicalPath - parameters.symRoot.canonicalPath == new File(projectDir, "symRoot").canonicalPath - parameters.sharedPrecompsDir.canonicalPath == new File(projectDir, "sharedPrecompsDir").canonicalPath - parameters.derivedDataPath.canonicalPath == new File(projectDir, "derivedDataPath").canonicalPath - - parameters.arch.size() == 1 - parameters.arch[0] == "i386" - - parameters.devices == Devices.UNIVERSAL - } - def "XcodebuildParameters get workspace from project"() { when: File projectDir = new File("../example/iOS/SwiftExample") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) extension.type = Type.iOS workspaceDirectory = new File(projectDir, "SwiftExample.xcworkspace") @@ -569,14 +519,14 @@ class XcodeBuildPluginExtensionSpecification extends Specification { def "bitcode is default false"() { expect: - extension.bitcode == false + extension.bitcode.get() == false } def "xcodeparameter is created with simulator true value"() { when: File projectDir = new File("../example/macOS/ExampleOSX") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) extension.simulator = true then: @@ -587,18 +537,18 @@ class XcodeBuildPluginExtensionSpecification extends Specification { when: File projectDir = new File("../example/macOS/ExampleOSX") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) - extension.bitcode = true + extension = new XcodeBuildPluginExtension(project, commandRunner) + extension.bitcode.set(true) then: - extension.getXcodebuildParameters().bitcode == true + extension.getXcodebuildParameters().bitcode } def "xcodebuildParameters is created with the applicationBundle"() { when: File projectDir = new File("../example/macOS/ExampleOSX") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, commandRunner) extension.bundleName = "MyApp" then: diff --git a/plugin/src/test/groovy/org/openbakery/XcodePluginSpecification.groovy b/plugin/src/test/groovy/org/openbakery/XcodePluginSpecification.groovy index 463729a4..1e62a0f9 100644 --- a/plugin/src/test/groovy/org/openbakery/XcodePluginSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/XcodePluginSpecification.groovy @@ -9,6 +9,9 @@ import org.gradle.testfixtures.ProjectBuilder import org.openbakery.appstore.AppstorePluginExtension import org.openbakery.appstore.AppstoreUploadTask import org.openbakery.appstore.AppstoreValidateTask +import org.openbakery.archiving.XcodeBuildArchiveTask +import org.openbakery.archiving.XcodeBuildArchiveTaskIosAndTvOS +import org.openbakery.archiving.XcodeBuildLegacyArchiveTask import org.openbakery.carthage.CarthageCleanTask import org.openbakery.carthage.CarthageUpdateTask import org.openbakery.cocoapods.CocoapodsBootstrapTask @@ -46,7 +49,9 @@ class XcodePluginSpecification extends Specification { def "contain task archive"() { expect: - project.tasks.findByName('archive') instanceof XcodeBuildArchiveTask + project.tasks.findByName(XcodeBuildArchiveTask.NAME) instanceof XcodeBuildArchiveTask + project.tasks.findByName(XcodeBuildLegacyArchiveTask.NAME) instanceof XcodeBuildLegacyArchiveTask + project.tasks.findByName(XcodeBuildArchiveTaskIosAndTvOS.NAME) instanceof XcodeBuildArchiveTaskIosAndTvOS } @@ -286,7 +291,7 @@ class XcodePluginSpecification extends Specification { project.tasks.findByName('carthageUpdate') instanceof CarthageUpdateTask } - def "xcodebuild has carthage dependency"() { + def "xcodebuild has carthage bootstrap dependency"() { when: File projectDir = new File("../example/iOS/Example") project = ProjectBuilder.builder().withProjectDir(projectDir).build() @@ -298,7 +303,7 @@ class XcodePluginSpecification extends Specification { then: - task.getTaskDependencies().getDependencies() contains(project.getTasks().getByName(XcodePlugin.CARTHAGE_UPDATE_TASK_NAME)) + task.getTaskDependencies().getDependencies() contains(project.getTasks().getByName(XcodePlugin.CARTHAGE_BOOTSTRAP_TASK_NAME)) } @@ -307,6 +312,11 @@ class XcodePluginSpecification extends Specification { project.tasks.findByName('carthageClean') instanceof CarthageCleanTask } + def "has carthage update task"() { + expect: + project.tasks.findByName('carthageUpdate') instanceof CarthageUpdateTask + } + def "clean has carthage clean dependency"() { when: File projectDir = new File("../example/iOS/Example") diff --git a/plugin/src/test/groovy/org/openbakery/XcodeTestRunTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/XcodeTestRunTaskSpecification.groovy index 50a39af2..1a31888d 100644 --- a/plugin/src/test/groovy/org/openbakery/XcodeTestRunTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/XcodeTestRunTaskSpecification.groovy @@ -6,6 +6,8 @@ import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.codesign.Codesign import org.openbakery.output.TestBuildOutputAppender +import org.openbakery.signing.KeychainCreateTask +import org.openbakery.signing.ProvisioningInstallTask import org.openbakery.test.TestResultParser import org.openbakery.testdouble.SimulatorControlStub import org.openbakery.testdouble.XcodeFake @@ -159,7 +161,7 @@ class XcodeTestRunTaskSpecification extends Specification { def "delete derivedData/Logs/Test before test is executed"() { project.xcodebuild.target = "Test" - def testDirectory = new File(project.xcodebuild.derivedDataPath, "Logs/Test") + def testDirectory = new File(project.xcodebuild.derivedDataPath.asFile.getOrNull(), "Logs/Test") FileUtils.writeStringToFile(new File(testDirectory, "foobar"), "dummy"); when: @@ -241,24 +243,10 @@ class XcodeTestRunTaskSpecification extends Specification { project.evaluate() then: - !xcodeTestRunTestTask.getTaskDependencies().getDependencies().contains(project.getTasks().getByName(XcodePlugin.KEYCHAIN_CREATE_TASK_NAME)) - !xcodeTestRunTestTask.getTaskDependencies().getDependencies().contains(project.getTasks().getByName(XcodePlugin.PROVISIONING_INSTALL_TASK_NAME)) + !xcodeTestRunTestTask.getTaskDependencies().getDependencies().contains(project.getTasks().getByName(KeychainCreateTask.TASK_NAME)) + !xcodeTestRunTestTask.getTaskDependencies().getDependencies().contains(project.getTasks().getByName(ProvisioningInstallTask.TASK_NAME)) } - def "when keychain dependency then also has finalized keychain remove"() { - when: - def bundleDirectory = createTestBundleForDeviceBuild() - xcodeTestRunTestTask = project.getTasks().getByPath(XcodePlugin.XCODE_TEST_RUN_TASK_NAME) - xcodeTestRunTestTask.setBundleDirectory(bundleDirectory) - project.evaluate() - - def finalized = xcodeTestRunTestTask.finalizedBy.getDependencies() - def keychainRemoveTask = project.getTasks().getByPath(XcodePlugin.KEYCHAIN_REMOVE_SEARCH_LIST_TASK_NAME) - then: - finalized.contains(keychainRemoveTask) - } - - def "has keychain dependency if device run"() { when: def bundleDirectory = createTestBundleForDeviceBuild() @@ -267,8 +255,8 @@ class XcodeTestRunTaskSpecification extends Specification { project.evaluate() then: - xcodeTestRunTestTask.getTaskDependencies().getDependencies().contains(project.getTasks().getByName(XcodePlugin.KEYCHAIN_CREATE_TASK_NAME)) - xcodeTestRunTestTask.getTaskDependencies().getDependencies().contains(project.getTasks().getByName(XcodePlugin.PROVISIONING_INSTALL_TASK_NAME)) + xcodeTestRunTestTask.getTaskDependencies().getDependencies().contains(project.getTasks().getByName(KeychainCreateTask.TASK_NAME)) + xcodeTestRunTestTask.getTaskDependencies().getDependencies().contains(project.getTasks().getByName(ProvisioningInstallTask.TASK_NAME)) } diff --git a/plugin/src/test/groovy/org/openbakery/XcodeTestTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/XcodeTestTaskSpecification.groovy index 09e4ffc6..d2c34731 100644 --- a/plugin/src/test/groovy/org/openbakery/XcodeTestTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/XcodeTestTaskSpecification.groovy @@ -518,7 +518,7 @@ class XcodeTestTaskSpecification extends Specification { mockXcodeVersion() project.xcodebuild.target = "Test" - def testDirectory = new File(project.xcodebuild.derivedDataPath, "Logs/Test") + def testDirectory = new File(project.xcodebuild.derivedDataPath.asFile.getOrNull(), "Logs/Test") FileUtils.writeStringToFile(new File(testDirectory, "foobar"), "dummy"); when: diff --git a/plugin/src/test/groovy/org/openbakery/archiving/XcodeBuildArchiveTaskIosAndTvOSTest.groovy b/plugin/src/test/groovy/org/openbakery/archiving/XcodeBuildArchiveTaskIosAndTvOSTest.groovy new file mode 100644 index 00000000..588f0a67 --- /dev/null +++ b/plugin/src/test/groovy/org/openbakery/archiving/XcodeBuildArchiveTaskIosAndTvOSTest.groovy @@ -0,0 +1,114 @@ +package org.openbakery.archiving + +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Rule +import org.junit.rules.ExpectedException +import org.junit.rules.TemporaryFolder +import org.openbakery.CommandRunner +import org.openbakery.XcodePlugin +import org.openbakery.XcodeService +import org.openbakery.xcode.Type +import spock.lang.Shared +import spock.lang.Specification + +import static org.openbakery.xcode.Xcodebuild.* + +class XcodeBuildArchiveTaskIosAndTvOSTest extends Specification { + + @Rule + public ExpectedException exception = ExpectedException.none() + + @Rule + final TemporaryFolder testProjectDir = new TemporaryFolder() + + Project project + XcodeBuildArchiveTaskIosAndTvOS subject + CommandRunner mockCommandRunner = Mock(CommandRunner) + XcodeService.XcodeApp mockXcodeApp = Mock(XcodeService.XcodeApp) + XcodeService mockXcodeService = Mock(XcodeService) + File outputDir + File fakeXccConfig + + @Shared + File fakeXcodeRuntime + + private static final String TEST_SCHEME = "test-scheme" + + void setup() { + project = ProjectBuilder.builder() + .withProjectDir(testProjectDir.root) + .build() + + project.buildDir = testProjectDir.newFile("build.gradle") + project.apply plugin: XcodePlugin + + outputDir = testProjectDir.newFolder("output") + fakeXccConfig = testProjectDir.newFile("test.xcconfig") + fakeXcodeRuntime = testProjectDir.newFile("test.app") + + subject = project.getTasks().getByName(XcodeBuildArchiveTaskIosAndTvOS.NAME) as XcodeBuildArchiveTaskIosAndTvOS + assert subject != null + + subject.xcodeServiceProperty.set(mockXcodeService) + subject.commandRunnerProperty.set(mockCommandRunner) + subject.scheme.set(TEST_SCHEME) + subject.buildType.set(Type.iOS) + subject.outputArchiveFile.set(outputDir) + subject.xcConfigFile.set(fakeXccConfig) + } + + def "The xcode archive should be called with the right configuration"() { + when: + subject.archive() + + then: + noExceptionThrown() + + 1 * mockCommandRunner.run([EXECUTABLE, + ACTION_ARCHIVE, + ARGUMENT_SCHEME, TEST_SCHEME, + ARGUMENT_ARCHIVE_PATH, outputDir.absolutePath, + ARGUMENT_XCCONFIG, fakeXccConfig.absolutePath], _) + } + + def "Should be able to customise xcode version"() { + when: + mockXcodeApp.contentDeveloperFile >> fakeXcodeRuntime + mockXcodeService.getInstallationForVersion(version) >> mockXcodeApp + + subject.xcodeVersion.set(version) + subject.archive() + + then: + noExceptionThrown() + + 1 * mockCommandRunner.run([EXECUTABLE, + ACTION_ARCHIVE, + ARGUMENT_SCHEME, TEST_SCHEME, + ARGUMENT_ARCHIVE_PATH, outputDir.absolutePath, + ARGUMENT_XCCONFIG, fakeXccConfig.absolutePath], + ['DEVELOPER_DIR': fakeXcodeRuntime.absolutePath.toString()]) + + where: + version | envValues + "9.3" | _ + "9.2" | _ + } + + def "The xcode version configuration should be optional"() { + when: + mockXcodeApp.contentDeveloperFile >> fakeXcodeRuntime + subject.xcodeVersion.set(null) + subject.archive() + + then: + noExceptionThrown() + + 1 * mockCommandRunner.run([EXECUTABLE, + ACTION_ARCHIVE, + ARGUMENT_SCHEME, TEST_SCHEME, + ARGUMENT_ARCHIVE_PATH, outputDir.absolutePath, + ARGUMENT_XCCONFIG, fakeXccConfig.absolutePath], [:]) + } +} diff --git a/plugin/src/test/groovy/org/openbakery/carthage/CarthageBootStrapTaskTest.groovy b/plugin/src/test/groovy/org/openbakery/carthage/CarthageBootStrapTaskTest.groovy new file mode 100644 index 00000000..8f45eeb5 --- /dev/null +++ b/plugin/src/test/groovy/org/openbakery/carthage/CarthageBootStrapTaskTest.groovy @@ -0,0 +1,150 @@ +package org.openbakery.carthage + +import org.gradle.api.Project +import org.gradle.api.provider.Provider +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Rule +import org.junit.rules.ExpectedException +import org.openbakery.CommandRunner +import org.openbakery.output.ConsoleOutputAppender +import org.openbakery.xcode.Xcode +import spock.lang.Specification +import spock.lang.Unroll + +import static org.openbakery.carthage.AbstractCarthageTaskBase.* +import static org.openbakery.xcode.Type.* + +class CarthageBootStrapTaskTest extends Specification { + + CarthageBootStrapTask subject + CommandRunner commandRunner = Mock(CommandRunner) + Xcode mockXcode = Mock(Xcode) + File projectDir + File cartFile + Project project + + @Rule + public ExpectedException exception = ExpectedException.none() + + void setup() { + projectDir = File.createTempDir() + + cartFile = new File(projectDir, "Cartfile") + cartFile << 'github "Alamofire/Alamofire"' + + project = ProjectBuilder.builder() + .withProjectDir(projectDir) + .build() + + project.buildDir = new File(projectDir, 'build').absoluteFile + project.apply plugin: org.openbakery.XcodePlugin + + subject = project.getTasks().getByPath('carthageBootstrap') + assert subject != null + + subject.commandRunner = commandRunner + } + + def "The carthage bootstrap task should be present"() { + expect: + subject instanceof CarthageBootStrapTask + } + + @Unroll + def "When bootstrap is executed should only update the platform: #platform"() { + given: + commandRunner.runWithResult("which", "carthage") >> "/usr/local/bin/carthage" + project.xcodebuild.type = platform + + when: + subject.update() + + then: + 1 * commandRunner.run(_, + [CARTHAGE_USR_BIN_PATH, + ACTION_BOOTSTRAP, + ARG_PLATFORM, + carthagePlatform, + ARG_CACHE_BUILDS] + , _ + , _) >> { + args -> args[3] instanceof ConsoleOutputAppender + } + + where: + platform | carthagePlatform + tvOS | CARTHAGE_PLATFORM_TVOS + macOS | CARTHAGE_PLATFORM_MACOS + watchOS | CARTHAGE_PLATFORM_WATCHOS + iOS | CARTHAGE_PLATFORM_IOS + } + + def "The task should not be executed if the 'Cartfile` file is missing"() { + given: + commandRunner.runWithResult("which", "carthage") >> "/usr/local/bin/carthage" + project.xcodebuild.type = platform + + when: + cartFile.delete() + subject.update() + + then: + 0 * commandRunner.run(_, + getCommandRunnerArgsForPlatform(carthagePlatform), + _, + _) >> { + args -> args[3] instanceof ConsoleOutputAppender + } + + where: + platform | carthagePlatform + tvOS | CARTHAGE_PLATFORM_TVOS + macOS | CARTHAGE_PLATFORM_MACOS + watchOS | CARTHAGE_PLATFORM_WATCHOS + iOS | CARTHAGE_PLATFORM_IOS + } + + def "The subject output directory should be platform dependant"() { + when: + subject.xcode.getXcodeSelectEnvValue(_) >> new HashMap() + project.xcodebuild.type = platform + + then: + Provider outputDirectory = subject.outputDirectory + outputDirectory.isPresent() + outputDirectory.get().name == carthagePlatform + + where: + platform | carthagePlatform + tvOS | CARTHAGE_PLATFORM_TVOS + macOS | CARTHAGE_PLATFORM_MACOS + watchOS | CARTHAGE_PLATFORM_WATCHOS + iOS | CARTHAGE_PLATFORM_IOS + } + + def "The xcode selection should be applied if a xcode version is defined"() { + when: + subject.xcode.getXcodeSelectEnvValue(_) >> new HashMap() + project.xcodebuild.type = iOS + project.xcodebuild.version = version + + subject.xcode = mockXcode + subject.xcode.setVersionFromString(_) >> _ + subject.update() + + then: + 1 * mockXcode.getXcodeSelectEnvValue(version) + + where: + version | _ + "7.1.1" | _ + } + + private List getCommandRunnerArgsForPlatform(String carthagePlatform) { + return [CARTHAGE_USR_BIN_PATH, + ACTION_UPDATE, + ARG_PLATFORM, + carthagePlatform, + ARG_CACHE_BUILDS] + } +} diff --git a/plugin/src/test/groovy/org/openbakery/carthage/CarthageUpdateTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/carthage/CarthageUpdateTaskSpecification.groovy index a0d2ca13..d5b43466 100644 --- a/plugin/src/test/groovy/org/openbakery/carthage/CarthageUpdateTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/carthage/CarthageUpdateTaskSpecification.groovy @@ -23,7 +23,7 @@ class CarthageUpdateTaskSpecification extends Specification { CommandRunner commandRunner = Mock(CommandRunner) @Rule - public ExpectedException exception = ExpectedException.none(); + public ExpectedException exception = ExpectedException.none() def setup() { projectDir = File.createTempDir() diff --git a/plugin/src/test/groovy/org/openbakery/coverage/CoverageTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/coverage/CoverageTaskSpecification.groovy index fb3f2608..63e0398d 100644 --- a/plugin/src/test/groovy/org/openbakery/coverage/CoverageTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/coverage/CoverageTaskSpecification.groovy @@ -36,7 +36,7 @@ class CoverageTaskSpecification extends Specification { def cleanup() { FileUtils.deleteDirectory(project.projectDir) - FileUtils.deleteDirectory(project.xcodebuild.derivedDataPath) + FileUtils.deleteDirectory(project.xcodebuild.derivedDataPath.asFile.getOrNull()) } @@ -116,7 +116,7 @@ class CoverageTaskSpecification extends Specification { coverageTask.binary = createFile("MyApp") coverageTask.report.commandRunner = Mock(org.openbakery.coverage.command.CommandRunner) - File profData = new File(project.xcodebuild.derivedDataPath, "Build/Intermediates/CodeCoverage/MyApp/Coverage.profdata") + File profData = new File(project.xcodebuild.derivedDataPath.asFile.getOrNull(), "Build/Intermediates/CodeCoverage/MyApp/Coverage.profdata") FileUtils.writeStringToFile(profData, "Dummy") when: @@ -135,7 +135,7 @@ class CoverageTaskSpecification extends Specification { destination.setId("MyAppID") project.coverage.setTestResultDestinations([destination]) - File profData = new File(project.xcodebuild.derivedDataPath, "Build/ProfileData/MyAppID/Coverage.profdata") + File profData = new File(project.xcodebuild.derivedDataPath.asFile.getOrNull(), "Build/ProfileData/MyAppID/Coverage.profdata") FileUtils.writeStringToFile(profData, "Dummy") when: @@ -172,7 +172,7 @@ class CoverageTaskSpecification extends Specification { coverageTask = localProject.getTasks().getByPath('coverage') coverageTask.report.commandRunner = Mock(org.openbakery.coverage.command.CommandRunner) - File profData = new File(localProject.xcodebuild.derivedDataPath, "Build/Intermediates/CodeCoverage/ExampleWatchkit/Coverage.profdata") + File profData = new File(localProject.xcodebuild.derivedDataPath.asFile.getOrNull(), "Build/Intermediates/CodeCoverage/ExampleWatchkit/Coverage.profdata") FileUtils.writeStringToFile(profData, "Dummy") when: @@ -328,7 +328,7 @@ class CoverageTaskSpecification extends Specification { coverageTask.binary = createFile("MyApp") coverageTask.report.commandRunner = Mock(org.openbakery.coverage.command.CommandRunner) - File profData = new File(project.xcodebuild.derivedDataPath, "Build/Intermediates/CodeCoverage/Coverage.profdata") + File profData = new File(project.xcodebuild.derivedDataPath.asFile.getOrNull(), "Build/Intermediates/CodeCoverage/Coverage.profdata") FileUtils.writeStringToFile(profData, "Dummy") when: diff --git a/plugin/src/test/groovy/org/openbakery/deploygate/DeployGateUploadTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/deploygate/DeployGateUploadTaskSpecification.groovy index 453ea54a..de8e1fce 100644 --- a/plugin/src/test/groovy/org/openbakery/deploygate/DeployGateUploadTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/deploygate/DeployGateUploadTaskSpecification.groovy @@ -3,9 +3,8 @@ package org.openbakery.deploygate import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder -import org.openbakery.XcodeBuildArchiveTask +import org.openbakery.util.PathHelper import spock.lang.Specification - /** * User: rene * Date: 11/11/14 @@ -31,7 +30,7 @@ class DeployGateUploadTaskSpecification extends Specification { File ipaBundle = new File(project.getBuildDir(), "package/Test.ipa") FileUtils.writeStringToFile(ipaBundle, "dummy") - File archiveDirectory = new File(project.getBuildDir(), XcodeBuildArchiveTask.ARCHIVE_FOLDER + "/Test.xcarchive") + File archiveDirectory = new File(PathHelper.resolveArchiveFolder(project), "Test.xcarchive") archiveDirectory.mkdirs() infoPlist = new File(archiveDirectory, "Products/Applications/Test.app/Info.plist"); diff --git a/plugin/src/test/groovy/org/openbakery/hockeyapp/HockeyAppTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/hockeyapp/HockeyAppTaskSpecification.groovy index 09ffd484..dc1f9ff8 100644 --- a/plugin/src/test/groovy/org/openbakery/hockeyapp/HockeyAppTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/hockeyapp/HockeyAppTaskSpecification.groovy @@ -4,9 +4,8 @@ import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.CommandRunner -import org.openbakery.XcodeBuildArchiveTask +import org.openbakery.util.PathHelper import spock.lang.Specification - /** * User: rene * Date: 11/11/14 @@ -36,7 +35,7 @@ class HockeyAppTaskSpecification extends Specification { File ipaBundle = new File(project.getBuildDir(), "package/Test.ipa") FileUtils.writeStringToFile(ipaBundle, "dummy") - File archiveDirectory = new File(project.getBuildDir(), XcodeBuildArchiveTask.ARCHIVE_FOLDER + "/Test.xcarchive") + File archiveDirectory = new File(PathHelper.resolveArchiveFolder(project), "Test.xcarchive") archiveDirectory.mkdirs() infoPlist = new File(archiveDirectory, "Products/Applications/Test.app/Info.plist"); diff --git a/plugin/src/test/groovy/org/openbakery/hockeykit/HockeyKitArchiveTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/hockeykit/HockeyKitArchiveTaskSpecification.groovy index a73dc8f1..67be1f1c 100644 --- a/plugin/src/test/groovy/org/openbakery/hockeykit/HockeyKitArchiveTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/hockeykit/HockeyKitArchiveTaskSpecification.groovy @@ -4,10 +4,9 @@ import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.CommandRunner +import org.openbakery.util.PathHelper import org.openbakery.util.PlistHelper -import org.openbakery.XcodeBuildArchiveTask import spock.lang.Specification - /** * User: rene * Date: 11/11/14 @@ -38,7 +37,7 @@ class HockeyKitArchiveTaskSpecification extends Specification { File ipaBundle = new File(project.getBuildDir(), "package/Test.ipa") FileUtils.writeStringToFile(ipaBundle, "dummy") - File archiveDirectory = new File(project.getBuildDir(), XcodeBuildArchiveTask.ARCHIVE_FOLDER + "/Test.xcarchive") + File archiveDirectory = new File(PathHelper.resolveArchiveFolder(project), "Test.xcarchive") archiveDirectory.mkdirs() infoPlist = new File(archiveDirectory, "Products/Applications/Test.app/Info.plist"); diff --git a/plugin/src/test/groovy/org/openbakery/hockeykit/HockeyKitManifestTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/hockeykit/HockeyKitManifestTaskSpecification.groovy index 52f940c1..2dcf7b85 100644 --- a/plugin/src/test/groovy/org/openbakery/hockeykit/HockeyKitManifestTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/hockeykit/HockeyKitManifestTaskSpecification.groovy @@ -5,7 +5,7 @@ import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.CommandRunner import org.openbakery.testdouble.PlistHelperStub -import org.openbakery.XcodeBuildArchiveTask +import org.openbakery.util.PathHelper import spock.lang.Specification class HockeyKitManifestTaskSpecification extends Specification { @@ -35,7 +35,7 @@ class HockeyKitManifestTaskSpecification extends Specification { hockeyKitManifestTask.commandRunner = commandRunner - File archiveDirectory = new File(project.getBuildDir(), XcodeBuildArchiveTask.ARCHIVE_FOLDER + "/Test.xcarchive") + File archiveDirectory = new File(PathHelper.resolveArchiveFolder(project), "Test.xcarchive") archiveDirectory.mkdirs() infoPlist = new File(archiveDirectory, "Products/Applications/Test.app/Info.plist"); diff --git a/plugin/src/test/groovy/org/openbakery/packaging/PackageTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/packaging/PackageLegacyTaskSpecification.groovy similarity index 93% rename from plugin/src/test/groovy/org/openbakery/packaging/PackageTaskSpecification.groovy rename to plugin/src/test/groovy/org/openbakery/packaging/PackageLegacyTaskSpecification.groovy index 0e036d2a..423a0955 100644 --- a/plugin/src/test/groovy/org/openbakery/packaging/PackageTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/packaging/PackageLegacyTaskSpecification.groovy @@ -6,25 +6,25 @@ import org.apache.commons.lang.RandomStringUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.CommandRunner -import org.openbakery.appledoc.AppledocCleanTask -import org.openbakery.test.ApplicationDummy -import org.openbakery.xcode.Extension -import org.openbakery.xcode.Type -import org.openbakery.XcodeBuildArchiveTask -import org.openbakery.XcodePlugin import org.openbakery.output.StyledTextOutputStub +import org.openbakery.signing.KeychainCreateTask +import org.openbakery.signing.ProvisioningInstallTask +import org.openbakery.test.ApplicationDummy import org.openbakery.testdouble.PlistHelperStub import org.openbakery.testdouble.XcodeFake +import org.openbakery.util.PathHelper +import org.openbakery.xcode.Extension +import org.openbakery.xcode.Type import spock.lang.Specification import java.util.zip.ZipEntry import java.util.zip.ZipFile -class PackageTaskSpecification extends Specification { +class PackageLegacyTaskSpecification extends Specification { Project project - PackageTask packageTask + PackageLegacyTask packageTask ApplicationDummy applicationDummy CommandRunner commandRunner = Mock(CommandRunner) @@ -56,7 +56,7 @@ class PackageTaskSpecification extends Specification { project.xcodebuild.signing.keychain = "/var/tmp/gradle.keychain" - packageTask = project.getTasks().getByPath(XcodePlugin.PACKAGE_TASK_NAME) + packageTask = project.getTasks().getByPath(PackageLegacyTask.NAME) packageTask.plistHelper = plistHelperStub packageTask.commandRunner = commandRunner @@ -86,9 +86,9 @@ class PackageTaskSpecification extends Specification { } void mockExampleApp(boolean withPlugin, boolean withSwift, boolean withFramework = false, boolean adHoc = true, boolean bitcode = false) { - outputPath = new File(project.getBuildDir(), packageTask.PACKAGE_PATH) + outputPath = PathHelper.resolvePackageFolder(project) - archiveDirectory = new File(project.getBuildDir(), XcodeBuildArchiveTask.ARCHIVE_FOLDER + "/Example.xcarchive") + archiveDirectory = new File(PathHelper.resolveArchiveFolder(project), "Example.xcarchive") File payloadDirectory = new File(outputPath, "Payload") payloadAppDirectory = new File(payloadDirectory, "Example.app"); @@ -211,7 +211,7 @@ class PackageTaskSpecification extends Specification { def "swift Framework xcode 6"() { given: mockXcodeVersion() - project.xcodebuild.version = 6 + project.xcodebuild.version.set("6") FileUtils.deleteDirectory(project.projectDir) mockExampleApp(false, true, false, false) @@ -229,7 +229,7 @@ class PackageTaskSpecification extends Specification { def "SwiftSupport should be added for Appstore IPA"() { given: mockXcodeVersion() - project.xcodebuild.version = 7 + project.xcodebuild.version.set("7") FileUtils.deleteDirectory(project.projectDir) mockExampleApp(false, true, false, false) @@ -247,7 +247,7 @@ class PackageTaskSpecification extends Specification { def "SwiftSupport should not be added for AdHoc IPA"() { given: mockXcodeVersion() - project.xcodebuild.version = 7 + project.xcodebuild.version.set("7") FileUtils.deleteDirectory(project.projectDir) mockExampleApp(false, true) @@ -441,20 +441,11 @@ class PackageTaskSpecification extends Specification { when: def dependsOn = packageTask.getDependsOn() then: - dependsOn.contains(XcodePlugin.KEYCHAIN_CREATE_TASK_NAME) - dependsOn.contains(XcodePlugin.PROVISIONING_INSTALL_TASK_NAME) + dependsOn.contains(KeychainCreateTask.TASK_NAME) + dependsOn.contains(ProvisioningInstallTask.TASK_NAME) } - def "finalized"() { - when: - def finalized = packageTask.finalizedBy.getDependencies() - def keychainRemoveTask = project.getTasks().getByPath(XcodePlugin.KEYCHAIN_REMOVE_SEARCH_LIST_TASK_NAME) - then: - finalized.contains(keychainRemoveTask) - } - - def "delete CFBundleResourceSpecification"() { given: mockExampleApp(false, true) diff --git a/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_OSXSpecification.groovy b/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_OSXSpecification.groovy index ff27bf8d..64d6fcb1 100644 --- a/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_OSXSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_OSXSpecification.groovy @@ -5,11 +5,10 @@ import org.apache.commons.io.FilenameUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.CommandRunner -import org.openbakery.xcode.Type -import org.openbakery.XcodeBuildArchiveTask -import org.openbakery.XcodePlugin import org.openbakery.testdouble.PlistHelperStub import org.openbakery.testdouble.XcodeFake +import org.openbakery.util.PathHelper +import org.openbakery.xcode.Type import spock.lang.Specification import java.util.zip.ZipEntry @@ -18,7 +17,7 @@ import java.util.zip.ZipFile class PackageTask_OSXSpecification extends Specification { Project project - PackageTask packageTask; + PackageLegacyTask packageTask; CommandRunner commandRunner = Mock(CommandRunner) @@ -57,7 +56,7 @@ class PackageTask_OSXSpecification extends Specification { project.xcodebuild.signing.keychain = keychain.absolutePath project.xcodebuild.signing.identity = 'iPhone Developer: Firstname Surename (AAAAAAAAAA)' - packageTask = project.getTasks().getByPath(XcodePlugin.PACKAGE_TASK_NAME) + packageTask = project.getTasks().getByPath(PackageLegacyTask.NAME) packageTask.plistHelper = plistHelperStub packageTask.commandRunner = commandRunner @@ -65,9 +64,9 @@ class PackageTask_OSXSpecification extends Specification { provisionLibraryPath = new File(System.getProperty("user.home") + "/Library/MobileDevice/Provisioning Profiles/"); - archiveDirectory = new File(project.getBuildDir(), XcodeBuildArchiveTask.ARCHIVE_FOLDER + "/Example.xcarchive") + archiveDirectory = new File(PathHelper.resolveArchiveFolder(project), "Example.xcarchive") - outputPath = new File(project.getBuildDir(), packageTask.PACKAGE_PATH) + outputPath = PathHelper.resolvePackageFolder(project) appDirectory = new File(outputPath, "Example.app"); diff --git a/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_WatchAppSpecification.groovy b/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_WatchAppSpecification.groovy index dda0a4f6..cc596e21 100644 --- a/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_WatchAppSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_WatchAppSpecification.groovy @@ -4,18 +4,17 @@ import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.CommandRunner -import org.openbakery.xcode.Type -import org.openbakery.XcodeBuildArchiveTask -import org.openbakery.XcodePlugin import org.openbakery.testdouble.PlistHelperStub import org.openbakery.testdouble.XcodeFake +import org.openbakery.util.PathHelper +import org.openbakery.xcode.Type import spock.lang.Specification class PackageTask_WatchAppSpecification extends Specification { Project project - PackageTask packageTask; + PackageLegacyTask packageTask; CommandRunner commandRunner = Mock(CommandRunner) @@ -49,7 +48,7 @@ class PackageTask_WatchAppSpecification extends Specification { project.xcodebuild.signing.keychain = "/var/tmp/gradle.keychain" - packageTask = project.getTasks().getByPath(XcodePlugin.PACKAGE_TASK_NAME) + packageTask = project.getTasks().getByPath(PackageLegacyTask.NAME) packageTask.plistHelper = plistHelperStub packageTask.commandRunner = commandRunner @@ -57,9 +56,9 @@ class PackageTask_WatchAppSpecification extends Specification { provisionLibraryPath = new File(System.getProperty("user.home") + "/Library/MobileDevice/Provisioning Profiles/"); - archiveDirectory = new File(project.getBuildDir(), XcodeBuildArchiveTask.ARCHIVE_FOLDER + "/Example.xcarchive") + archiveDirectory = new File(PathHelper.resolveArchiveFolder(project), "Example.xcarchive") - outputPath = new File(project.getBuildDir(), packageTask.PACKAGE_PATH) + outputPath = PathHelper.resolvePackageFolder(project) File payloadDirectory = new File(outputPath, "Payload") payloadAppDirectory = new File(payloadDirectory, "ExampleWatchKit.app"); diff --git a/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_WatchKitSpecification.groovy b/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_WatchKitSpecification.groovy index 3e13af3e..3f56177a 100644 --- a/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_WatchKitSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/packaging/PackageTask_WatchKitSpecification.groovy @@ -4,18 +4,17 @@ import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.CommandRunner -import org.openbakery.xcode.Type -import org.openbakery.XcodeBuildArchiveTask -import org.openbakery.XcodePlugin import org.openbakery.testdouble.PlistHelperStub import org.openbakery.testdouble.XcodeFake +import org.openbakery.util.PathHelper +import org.openbakery.xcode.Type import spock.lang.Specification class PackageTask_WatchKitSpecification extends Specification { Project project - PackageTask packageTask; + PackageLegacyTask packageTask; CommandRunner commandRunner = Mock(CommandRunner) @@ -45,7 +44,7 @@ class PackageTask_WatchKitSpecification extends Specification { project.xcodebuild.signing.keychain = "/var/tmp/gradle.keychain" - packageTask = project.getTasks().getByPath(XcodePlugin.PACKAGE_TASK_NAME) + packageTask = project.getTasks().getByPath(PackageLegacyTask.NAME) packageTask.plistHelper = plistHelperStub packageTask.commandRunner = commandRunner @@ -53,9 +52,9 @@ class PackageTask_WatchKitSpecification extends Specification { provisionLibraryPath = new File(System.getProperty("user.home") + "/Library/MobileDevice/Provisioning Profiles/"); - archiveDirectory = new File(project.getBuildDir(), XcodeBuildArchiveTask.ARCHIVE_FOLDER + "/Example.xcarchive") + archiveDirectory = new File(PathHelper.resolveArchiveFolder(project), "Example.xcarchive") - outputPath = new File(project.getBuildDir(), packageTask.PACKAGE_PATH) + outputPath = PathHelper.resolvePackageFolder(project) File payloadDirectory = new File(outputPath, "Payload") payloadAppDirectory = new File(payloadDirectory, "Example.app"); diff --git a/plugin/src/test/groovy/org/openbakery/packaging/ReleaseNotesTest.groovy b/plugin/src/test/groovy/org/openbakery/packaging/ReleaseNotesTest.groovy index c5cdd4ef..baba8159 100644 --- a/plugin/src/test/groovy/org/openbakery/packaging/ReleaseNotesTest.groovy +++ b/plugin/src/test/groovy/org/openbakery/packaging/ReleaseNotesTest.groovy @@ -4,7 +4,7 @@ import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.XcodePlugin -import org.openbakery.packaging.ReleaseNotesTask +import org.openbakery.ReleaseNotesTask import org.junit.Before import org.junit.After import org.junit.Test diff --git a/plugin/src/test/groovy/org/openbakery/signing/KeychainCleanupTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/signing/KeychainCleanupTaskSpecification.groovy deleted file mode 100644 index c649a011..00000000 --- a/plugin/src/test/groovy/org/openbakery/signing/KeychainCleanupTaskSpecification.groovy +++ /dev/null @@ -1,151 +0,0 @@ -package org.openbakery.signing - -import org.apache.commons.io.FileUtils -import org.gradle.api.Project -import org.gradle.testfixtures.ProjectBuilder -import org.openbakery.CommandRunner -import org.openbakery.XcodeBuildPluginExtension -import org.openbakery.XcodePlugin -import spock.lang.Specification - - -class KeychainCleanupTaskSpecification extends Specification { - - Project project - - KeychainCleanupTask keychainCleanupTask - - CommandRunner commandRunner = Mock(CommandRunner) - File keychainDestinationFile - File certificateFile - - File tmpDirectory - File loginKeychain - - def setup() { - File projectDir = new File("../example/iOS/ExampleWatchkit") - project = ProjectBuilder.builder().withProjectDir(projectDir).build() - tmpDirectory = new File(System.getProperty("java.io.tmpdir"), 'gradle-xcodebuild') - project.buildDir = new File(tmpDirectory, 'build').absoluteFile - - loginKeychain = new File(tmpDirectory, "login.keychain") - FileUtils.writeStringToFile(loginKeychain, "dummy") - - - project.apply plugin: org.openbakery.XcodePlugin - - keychainCleanupTask = project.tasks.findByName(XcodePlugin.KEYCHAIN_CLEAN_TASK_NAME); - keychainCleanupTask.commandRunner = commandRunner - keychainCleanupTask.security.commandRunner = commandRunner - - - certificateFile = File.createTempFile("test", ".cert") - certificateFile.deleteOnExit() - keychainDestinationFile = new File(project.xcodebuild.signing.signingDestinationRoot, certificateFile.getName()) - - } - - def cleanup() { - FileUtils.deleteDirectory(project.buildDir) - FileUtils.deleteDirectory(tmpDirectory) - } - - def "delete keychain OS X 10.8"() { - given: - System.setProperty("os.version", "10.8.0"); - - String result = " \""+ loginKeychain.absolutePath + "\"\n" + - " \"/Library/Keychains/" + XcodeBuildPluginExtension.KEYCHAIN_NAME_BASE + "delete-me.keychain\"\n" + - " \"/Library/Keychains/System.keychain\""; - commandRunner.runWithResult(["security", "list-keychains"]) >> result - - when: - keychainCleanupTask.clean() - - then: - 1 * commandRunner.run(["security", "list-keychains", "-s", loginKeychain.absolutePath]) - - } - - def "delete keychain OS X 10.9"() { - given: - System.setProperty("os.version", "10.9.0"); - - String result = " \""+ loginKeychain.absolutePath + "\"\n" + - " \"/Library/Keychains/" + XcodeBuildPluginExtension.KEYCHAIN_NAME_BASE + "delete-me.keychain\"\n" + - " \"/Library/Keychains/System.keychain\""; - - commandRunner.runWithResult(["security", "list-keychains"]) >> result - - when: - keychainCleanupTask.clean() - - then: - 1 * commandRunner.run(["security", "list-keychains", "-s", loginKeychain.absolutePath]) - - } - - String getSecurityList() { - return " \""+ loginKeychain.absolutePath + "\n" + - " \"/Users/me/Go/pipelines/Build-Appstore/build/codesign/gradle-1431356246879.keychain\"\n" + - " \"/Users/me/Go/pipelines/Build-Test/build/codesign/gradle-1431356877451.keychain\"\n" + - " \"/Users/me/Go/pipelines/Build-Continuous/build/codesign/gradle-1431419900260.keychain\"\n" + - " \"/Library/Keychains/System.keychain\"" - - } - - - def "keychain list update"() { - given: - commandRunner.runWithResult(["security", "list-keychains"]) >> getSecurityList() - - when: - keychainCleanupTask.removeGradleKeychainsFromSearchList() - - then: - 1 * commandRunner.run(["security", "list-keychains", "-s", loginKeychain.absolutePath]) - - } - - - def "get keychain list"() { - given: - commandRunner.runWithResult(["security", "list-keychains"]) >> getSecurityList() - - when: - List keychainList = keychainCleanupTask.getKeychainList() - - then: - keychainList.size == 1 - } - - - def "remove only missing keychain in list"() { - given: - System.setProperty("os.version", "10.9.0"); - - File keychainFile = new File(project.buildDir, "gradle.keychain"); - FileUtils.writeStringToFile(keychainFile, "dummy"); - - String keychainFileName = keychainFile.absolutePath - - String result = " \"" + loginKeychain.absolutePath + "\n" + - " \"" + keychainFileName + "\n" + - " \"/Library/Keychains/System.keychain\""; - - commandRunner.runWithResult(["security", "list-keychains"]) >> result - - - def commandList - - when: - keychainCleanupTask.removeGradleKeychainsFromSearchList() - - then: - 1 * commandRunner.run(_) >> { arguments -> commandList = arguments[0] } - commandList == ["security", "list-keychains", "-s", loginKeychain.absolutePath, keychainFileName] - //1 * commandRunner.run(["security", "list-keychains", "-s", "/Users/me/Library/Keychains/login.keychain", keychainFileName]) - - } - -} diff --git a/plugin/src/test/groovy/org/openbakery/signing/KeychainCreateTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/signing/KeychainCreateTaskSpecification.groovy index 36e2579d..af4bb901 100644 --- a/plugin/src/test/groovy/org/openbakery/signing/KeychainCreateTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/signing/KeychainCreateTaskSpecification.groovy @@ -3,172 +3,173 @@ package org.openbakery.signing import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder +import org.junit.Rule +import org.junit.rules.TemporaryFolder import org.openbakery.CommandRunner +import org.openbakery.XcodeBuildPluginExtension import org.openbakery.XcodePlugin import org.openbakery.codesign.Security import org.openbakery.xcode.Type -import org.openbakery.xcode.Version import spock.lang.Specification +import spock.lang.Unroll + +import static org.openbakery.signing.KeychainCreateTask.KEYCHAIN_DEFAULT_PASSWORD class KeychainCreateTaskSpecification extends Specification { + @Rule + final TemporaryFolder tmpDirectory = new TemporaryFolder() + + KeychainCreateTask subject + Project project - KeychainCreateTask keychainCreateTask CommandRunner commandRunner = Mock(CommandRunner) File keychainDestinationFile File certificateFile - File tmpDirectory File loginKeychain + File folder + XcodeBuildPluginExtension xcodeBuildPluginExtension + Security mockSecurity - def setup() { - tmpDirectory = new File(System.getProperty("java.io.tmpdir"), 'gradle-xcodebuild') + final static String CERTIFICATE_PASSWORD = "password" + final static String FAKE_CERT_CONTENT = "Bag Attributes\n" + + " localKeyID: FE 93 19 AC CC D7 C1 AC 82 97 02 C2 35 97 B6 CE 37 33 CB 4F\n" + + " friendlyName: iPhone Distribution: Test Company Name (12345ABCDE)" + def setup() { project = ProjectBuilder.builder().build() project.buildDir = new File('build').absoluteFile - project.apply plugin: org.openbakery.XcodePlugin + project.apply plugin: XcodePlugin + + mockSecurity = Mock(Security) - keychainCreateTask = project.tasks.findByName('keychainCreate') - keychainCreateTask.commandRunner = commandRunner - keychainCreateTask.security.commandRunner = commandRunner + subject = project.tasks.findByName('keychainCreate') as KeychainCreateTask + subject.security.set(new Security(commandRunner)) + subject.commandRunnerProperty.set(commandRunner) + xcodeBuildPluginExtension = project.extensions.getByType(XcodeBuildPluginExtension) + folder = xcodeBuildPluginExtension.signing + .signingDestinationRoot + .get() + .asFile - certificateFile = File.createTempFile("test", ".cert") - keychainDestinationFile = new File(project.xcodebuild.signing.signingDestinationRoot, certificateFile.getName()) + certificateFile = tmpDirectory.newFile("test.cert") + certificateFile.text = FAKE_CERT_CONTENT - loginKeychain = new File(tmpDirectory, "login.keychain") + keychainDestinationFile = new File(folder, + certificateFile.getName()) + + loginKeychain = tmpDirectory.newFile("login.keychain") FileUtils.writeStringToFile(loginKeychain, "dummy") project.xcodebuild.type = Type.macOS - project.xcodebuild.signing.certificateURI = certificateFile.toURL() - project.xcodebuild.signing.certificatePassword = "password" + project.xcodebuild.signing.certificatePassword = CERTIFICATE_PASSWORD project.xcodebuild.signing.timeout = null + subject.security.set(mockSecurity) + mockSecurity.getKeychainList() >> [] + commandRunner.runWithResult(_) >> FAKE_CERT_CONTENT } - def cleanup() { - certificateFile.delete() - new File(project.xcodebuild.signing.signingDestinationRoot, certificateFile.getName()).delete() - project.xcodebuild.signing.keychainPathInternal.delete() - FileUtils.deleteDirectory(tmpDirectory) - } - - - def "OSVersion"() { - System.setProperty("os.version", "10.9.0"); - - when: - Version version = keychainCreateTask.getOSVersion() - - then: - version != null; - version.major == 10 - version.minor == 9 - version.maintenance == 0 - } - - - def "create with OS X 10.8"() { + @Unroll + def "Mac OS #version - Temporary keychain should be create and certificate URI imported"() { given: - System.setProperty("os.version", "10.8.0") - - Security security = Mock(Security) - keychainCreateTask.security = security - security.getKeychainList() >> [] + System.setProperty("os.version", version) + project.xcodebuild.signing.certificate = certificateFile when: - keychainCreateTask.create() - - then: - 1 * security.createKeychain(project.xcodebuild.signing.keychainPathInternal, "This_is_the_default_keychain_password") - 1 * security.importCertificate(keychainDestinationFile, "password", project.xcodebuild.signing.keychainPathInternal) - 0 * security.setKeychainList([project.xcodebuild.signing.keychainPathInternal]) + subject.download() + + then: "The `setPartitionList` method should be call only for OS version > 10.12" + 1 * mockSecurity.createKeychain(xcodeBuildPluginExtension.signing.keyChainFile.asFile.get(), + KEYCHAIN_DEFAULT_PASSWORD) + + 1 * mockSecurity.importCertificate(keychainDestinationFile, + CERTIFICATE_PASSWORD, + xcodeBuildPluginExtension.signing.keyChainFile.asFile.get()) + + count * mockSecurity.setPartitionList(xcodeBuildPluginExtension.signing.keyChainFile.asFile.get(), + KEYCHAIN_DEFAULT_PASSWORD) + + where: + version | count + "10.8.0" | 0 + "10.9.0" | 0 + "10.11.0" | 0 + "10.12.0" | 1 + "11.12.0" | 1 + "11.12" | 1 } - - def mockListKeychains() { - String result = " \""+ loginKeychain.absolutePath + "\""; - commandRunner.runWithResult( ["security", "list-keychains"]) >> result - } - - def "create with OS X 10.9 adds keychain to list"() { + @Unroll + def "Mac OS #version - Temporary keychain should be create and certificate File imported"() { given: - System.setProperty("os.version", "10.9.0") - - Security security = Mock(Security) - keychainCreateTask.security = security - security.getKeychainList() >> [] + System.setProperty("os.version", version) + project.xcodebuild.signing.certificate = certificateFile when: - keychainCreateTask.create() - - then: - 1 * security.createKeychain(project.xcodebuild.signing.keychainPathInternal, "This_is_the_default_keychain_password") - 1 * security.importCertificate(keychainDestinationFile, "password", project.xcodebuild.signing.keychainPathInternal) - 1 * security.setKeychainList([project.xcodebuild.signing.keychainPathInternal]) + subject.download() + + then: "The `setPartitionList` method should be call only for OS version > 10.12" + 1 * mockSecurity.createKeychain(xcodeBuildPluginExtension.signing.keyChainFile.asFile.get(), + KEYCHAIN_DEFAULT_PASSWORD) + + 1 * mockSecurity.importCertificate(keychainDestinationFile, + CERTIFICATE_PASSWORD, + xcodeBuildPluginExtension.signing.keyChainFile.asFile.get()) + + count * mockSecurity.setPartitionList(xcodeBuildPluginExtension.signing.keyChainFile.asFile.get(), + KEYCHAIN_DEFAULT_PASSWORD) + + where: + version | count + "10.8.0" | 0 + "10.9.0" | 0 + "10.11.0" | 0 + "10.12.0" | 1 + "11.12.0" | 1 + "11.12" | 1 } - - def "cleanup first"() { + @Unroll + def "The keychain timeout should be called"() { given: - Security security = Mock(Security) - keychainCreateTask.security = security - security.getKeychainList() >> [] - File toBeDeleted = new File(project.xcodebuild.signing.signingDestinationRoot, "my.keychain") - FileUtils.writeStringToFile(toBeDeleted, "dummy") - mockListKeychains() - - when: - keychainCreateTask.create() - - then: - !toBeDeleted.exists() - - } + project.xcodebuild.signing.certificate = certificateFile + if (timeout) + xcodeBuildPluginExtension.signing.timeout.set(timeout) - def "depends on"() { when: - def dependsOn = keychainCreateTask.getDependsOn() - then: - !dependsOn.contains(XcodePlugin.KEYCHAIN_CLEAN_TASK_NAME) - } - - - def "create with macOS 10.12.0 set-key-partition-list"() { - given: - System.setProperty("os.version", "10.12.0") - - Security security = Mock(Security) - keychainCreateTask.security = security - security.getKeychainList() >> [] - - when: - keychainCreateTask.create() + subject.download() then: - 1 * security.createKeychain(project.xcodebuild.signing.keychainPathInternal, "This_is_the_default_keychain_password") - 1 * security.importCertificate(keychainDestinationFile, "password", project.xcodebuild.signing.keychainPathInternal) - 1 * security.setPartitionList(project.xcodebuild.signing.keychainPathInternal, "This_is_the_default_keychain_password") + count * mockSecurity.setTimeout(timeout, + xcodeBuildPluginExtension.signing.keyChainFile.asFile.get()) + + where: + timeout | count + 100 | 1 + 400 | 1 + null | 0 } - - def "create with macOS 10.11.0 NOT set-key-partition-list"() { + def "The temporary keychain file should be present post run"() { given: - System.setProperty("os.version", "10.11.0") + project.xcodebuild.signing.certificate = certificateFile - Security security = Mock(Security) - keychainCreateTask.security = security - security.getKeychainList() >> [] when: - keychainCreateTask.create() + subject.download() then: - 1 * security.createKeychain(project.xcodebuild.signing.keychainPathInternal, "This_is_the_default_keychain_password") - 1 * security.importCertificate(keychainDestinationFile, "password", project.xcodebuild.signing.keychainPathInternal) - 0 * security.setPartitionList(project.xcodebuild.signing.keychainPathInternal, "This_is_the_default_keychain_password") + File file = xcodeBuildPluginExtension.signing + .signingDestinationRoot + .file(certificateFile.name) + .get() + .asFile + file.exists() + file.text == FAKE_CERT_CONTENT } - } diff --git a/plugin/src/test/groovy/org/openbakery/signing/KeychainRemoveFromSearchListTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/signing/KeychainRemoveFromSearchListTaskSpecification.groovy deleted file mode 100644 index f72ef2b1..00000000 --- a/plugin/src/test/groovy/org/openbakery/signing/KeychainRemoveFromSearchListTaskSpecification.groovy +++ /dev/null @@ -1,156 +0,0 @@ -package org.openbakery.signing - -import groovy.mock.interceptor.MockFor -import org.apache.commons.io.FileUtils -import org.gradle.api.Project -import org.gradle.testfixtures.ProjectBuilder -import org.junit.Test -import org.openbakery.CommandRunner -import org.openbakery.XcodePlugin -import spock.lang.Specification - -/** - * User: rene - * Date: 14/10/15 - */ -class KeychainRemoveFromSearchListTaskSpecification extends Specification { - - Project project - - KeychainRemoveFromSearchListTask task - File tmpDirectory - File loginKeychain - CommandRunner commandRunner = Mock(CommandRunner) - - def setup() { - File projectDir = new File("../example/iOS/ExampleWatchkit") - - tmpDirectory = new File(System.getProperty("java.io.tmpdir"), 'gradle-xcodebuild') - - project = ProjectBuilder.builder().withProjectDir(projectDir).build() - project.buildDir = new File(tmpDirectory, 'build').absoluteFile - - project.apply plugin: org.openbakery.XcodePlugin - - task = project.tasks.findByName(XcodePlugin.KEYCHAIN_REMOVE_SEARCH_LIST_TASK_NAME); - task.commandRunner = commandRunner - task.security.commandRunner = commandRunner - - loginKeychain = new File(tmpDirectory, "login.keychain") - FileUtils.writeStringToFile(loginKeychain, "dummy") - - - } - - def cleanup() { - FileUtils.deleteDirectory(project.buildDir) - FileUtils.deleteDirectory(tmpDirectory) - } - - def "check group"() { - when: - true - - then: - task.group == XcodePlugin.XCODE_GROUP_NAME - } - - - String createSecurityListResult(String... keychainFiles) { - - StringBuilder builder = new StringBuilder(); - builder.append(" \"") - builder.append(loginKeychain.absolutePath) - builder.append("\"\n") - - - for (String keychainFile in keychainFiles) { - builder.append(" \"") - builder.append(keychainFile) - builder.append("\"\n") - } - - builder.append(" \"/Library/Keychains/System.keychain\"") - return builder.toString() - } - - def "remove"() { - def commandList; - - given: - def securityList = createSecurityListResult( - "/Users/me/Go/pipelines/Build-Appstore/build/codesign/gradle-1431356246879.keychain", - "/Users/me/Go/pipelines/Build-Test/build/codesign/gradle-1431356877451.keychain", - "/Users/me/Go/pipelines/Build-Continuous/build/codesign/gradle-1431419900260.keychain" - ) - - commandRunner.runWithResult(["security", "list-keychains"]) >> securityList - - when: - task.remove() - - then: - 1 * commandRunner.run(_) >> { arguments -> commandList = arguments[0] } - commandList == ["security", "list-keychains", "-s", loginKeychain.absolutePath] - - } - - def "remove with existing files"() { - def commandList; - - given: - File dummyKeychain = new File(project.buildDir, "gradle-1234.keychain") - FileUtils.writeStringToFile(dummyKeychain, "dummy"); - def securityList = createSecurityListResult(dummyKeychain.getAbsolutePath()) - commandRunner.runWithResult(["security", "list-keychains"]) >> securityList - - when: - task.remove() - - then: - 1 * commandRunner.run(_) >> { arguments -> commandList = arguments[0] } - commandList == ["security", "list-keychains", "-s", loginKeychain.absolutePath, dummyKeychain.getAbsolutePath()] - } - - - def "remove current used keychain"() { - def commandList; - - given: - File dummyKeychain = new File(project.buildDir, "gradle-1234.keychain") - FileUtils.writeStringToFile(dummyKeychain, "dummy"); - def securityList = createSecurityListResult(dummyKeychain.getAbsolutePath()) - commandRunner.runWithResult(["security", "list-keychains"]) >> securityList - - project.xcodebuild.signing.keychain = dummyKeychain - - when: - task.remove() - - then: - 1 * commandRunner.run(_) >> { arguments -> commandList = arguments[0] } - commandList == ["security", "list-keychains", "-s", loginKeychain.absolutePath] - } - - - def "remove current used internal keychain"() { - def commandList; - - given: - project.xcodebuild.signing.signingDestinationRoot = project.buildDir - - FileUtils.writeStringToFile(project.xcodebuild.signing.keychainPathInternal, "dummy"); - def securityList = createSecurityListResult(project.xcodebuild.signing.keychainPathInternal.getAbsolutePath()) - commandRunner.runWithResult(["security", "list-keychains"]) >> securityList - - when: - task.remove() - - then: - 1 * commandRunner.run(_) >> { arguments -> commandList = arguments[0] } - commandList == ["security", "list-keychains", "-s", loginKeychain.absolutePath] - } - - - -} diff --git a/plugin/src/test/groovy/org/openbakery/signing/ProvisioningInstallTaskOSXSpecification.groovy b/plugin/src/test/groovy/org/openbakery/signing/ProvisioningInstallTaskOSXSpecification.groovy index a7e78359..50078200 100644 --- a/plugin/src/test/groovy/org/openbakery/signing/ProvisioningInstallTaskOSXSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/signing/ProvisioningInstallTaskOSXSpecification.groovy @@ -6,7 +6,6 @@ import org.gradle.testfixtures.ProjectBuilder import org.openbakery.CommandRunner import org.openbakery.codesign.ProvisioningProfileReader import org.openbakery.xcode.Type -import org.openbakery.XcodePlugin import spock.lang.Specification class ProvisioningInstallTaskOSXSpecification extends Specification { @@ -30,9 +29,10 @@ class ProvisioningInstallTaskOSXSpecification extends Specification { project.xcodebuild.type = Type.macOS - provisioningInstallTask = project.getTasks().getByPath(XcodePlugin.PROVISIONING_INSTALL_TASK_NAME) + provisioningInstallTask = project.getTasks() + .getByPath(ProvisioningInstallTask.TASK_NAME) - provisioningInstallTask.commandRunner = commandRunner + provisioningInstallTask.commandRunnerProperty.set(commandRunner) provisionLibraryPath = new File(System.getProperty("user.home") + "/Library/MobileDevice/Provisioning Profiles/"); @@ -49,21 +49,21 @@ class ProvisioningInstallTaskOSXSpecification extends Specification { File testMobileprovision = new File("src/test/Resource/test-wildcard-mac.provisionprofile") project.xcodebuild.signing.mobileProvisionURI = testMobileprovision.toURI().toString() - ProvisioningProfileReader provisioningProfileIdReader = new ProvisioningProfileReader(testMobileprovision, commandRunner) - String uuid = provisioningProfileIdReader.getUUID() + ProvisioningProfileReader reader = new ProvisioningProfileReader(testMobileprovision, commandRunner) + String uuid = reader.getUUID() String name = "gradle-" + uuid + ".provisionprofile"; File source = new File(projectDir, "build/provision/" + name) File destination = new File(provisionLibraryPath, name) when: - provisioningInstallTask.install() + provisioningInstallTask.download() File sourceFile = new File(projectDir, "build/provision/" + name) then: sourceFile.exists() - 1 * commandRunner.run(["/bin/ln", "-s", source.absolutePath , destination.absolutePath]) + 1 * commandRunner.run(["/bin/ln", "-s", source.absolutePath, destination.absolutePath]) } def "multiple ProvisioningProfiles"() { @@ -85,7 +85,7 @@ class ProvisioningInstallTaskOSXSpecification extends Specification { File secondDestination = new File(provisionLibraryPath, secondName) when: - provisioningInstallTask.install() + provisioningInstallTask.download() then: firstFile.exists() diff --git a/plugin/src/test/groovy/org/openbakery/signing/ProvisioningInstallTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/signing/ProvisioningInstallTaskSpecification.groovy index 784ac105..43516ea3 100644 --- a/plugin/src/test/groovy/org/openbakery/signing/ProvisioningInstallTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/signing/ProvisioningInstallTaskSpecification.groovy @@ -1,130 +1,173 @@ package org.openbakery.signing -import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder +import org.junit.Rule +import org.junit.rules.TemporaryFolder import org.openbakery.CommandRunner -import org.openbakery.codesign.ProvisioningProfileReader +import org.openbakery.XcodeBuildPluginExtension import org.openbakery.XcodePlugin +import org.openbakery.codesign.ProvisioningProfileReader import spock.lang.Specification - class ProvisioningInstallTaskSpecification extends Specification { - Project project - ProvisioningInstallTask provisioningInstallTask; + @Rule + final TemporaryFolder tmpDirectory = new TemporaryFolder() + File testMobileProvision1 + File testMobileProvision2 + Project project + ProvisioningInstallTask subject CommandRunner commandRunner = Mock(CommandRunner) - File provisionLibraryPath - File projectDir + def setup() { + project = ProjectBuilder.builder().withProjectDir(tmpDirectory.root).build() + project.apply plugin: XcodePlugin + subject = project.tasks.findByName(ProvisioningInstallTask.TASK_NAME) as ProvisioningInstallTask - def setup() { + testMobileProvision1 = new File("../libtest/src/main/Resource/test.mobileprovision") + testMobileProvision2 = new File("src/test/Resource/test1.mobileprovision") - projectDir = new File(System.getProperty("java.io.tmpdir"), "gradle-xcodebuild") + assert subject != null + assert testMobileProvision1.exists() + assert testMobileProvision2.exists() + } - project = ProjectBuilder.builder().withProjectDir(projectDir).build() - project.buildDir = new File(projectDir, 'build').absoluteFile - project.apply plugin: org.openbakery.XcodePlugin + private String formatProvisionFileName(File file) { + assert file.exists() - project.xcodebuild.simulator = false + ProvisioningProfileReader provisioningProfileIdReader = new ProvisioningProfileReader(file, commandRunner) + String uuid = provisioningProfileIdReader.getUUID() + String name = "gradle-" + uuid + ".mobileprovision" - provisioningInstallTask = project.getTasks().getByPath(XcodePlugin.PROVISIONING_INSTALL_TASK_NAME) + return name + } - provisioningInstallTask.commandRunner = commandRunner + def "Should process without error a single provisioning file"() { + setup: + project.xcodebuild.signing.mobileProvisionURI = testMobileProvision2.toURI().toString() - provisionLibraryPath = new File(System.getProperty("user.home") + "/Library/MobileDevice/Provisioning Profiles/"); + String name2 = formatProvisionFileName(testMobileProvision2) - } + File downloadedFile2 = new File(tmpDirectory.root, "build/provision/" + name2) + File libraryFile2 = new File(ProvisioningInstallTask.PROVISIONING_DIR, name2) - def cleanup() { - FileUtils.deleteDirectory(projectDir) - } + when: + subject.download() + then: + noExceptionThrown() - def "single ProvisioningProfile"() { + and: + downloadedFile2.text == testMobileProvision2.text + downloadedFile2.exists() - File testMobileprovision = new File("../libtest/src/main/Resource/test.mobileprovision") - project.xcodebuild.signing.mobileProvisionURI = testMobileprovision.toURI().toString() + libraryFile2.text == testMobileProvision2.text + libraryFile2.exists() + } - ProvisioningProfileReader provisioningProfileIdReader = new ProvisioningProfileReader(testMobileprovision, commandRunner) - String uuid = provisioningProfileIdReader.getUUID() - String name = "gradle-" + uuid + ".mobileprovision"; + def "Should process without error a change of build folder"() { + setup: + String alternateBuildFolderName = "alternativeBuildFolder" + project.buildDir = tmpDirectory.newFolder(alternateBuildFolderName) + project.xcodebuild.signing.mobileProvisionURI = testMobileProvision2.toURI().toString() - File source = new File(projectDir, "build/provision/" + name) - File destination = new File(provisionLibraryPath, name) + String name2 = formatProvisionFileName(testMobileProvision2) - File sourceFile = new File(projectDir, "build/provision/" + name) + File downloadedFile2 = new File(tmpDirectory.root, "${alternateBuildFolderName}/provision/" + name2) + File libraryFile2 = new File(ProvisioningInstallTask.PROVISIONING_DIR, name2) when: - provisioningInstallTask.install() + subject.download() then: - sourceFile.exists() - 1 * commandRunner.run(["/bin/ln", "-s", source.absolutePath , destination.absolutePath]) + noExceptionThrown() - } + and: + downloadedFile2.text == testMobileProvision2.text + downloadedFile2.exists() - def "multiple ProvisioningProfiles"() { + libraryFile2.text == testMobileProvision2.text + libraryFile2.exists() + } - File firstMobileprovision = new File("../libtest/src/main/Resource/test.mobileprovision") - File secondMobileprovision = new File("src/test/Resource/test1.mobileprovision") - project.xcodebuild.signing.mobileProvisionURI = [firstMobileprovision.toURI().toString(), secondMobileprovision.toURI().toString() ] + def "Should process without error multiple provisioning files by using the deprecated `setMobileProvisionURI` property"() { - String firstName = "gradle-" + new ProvisioningProfileReader(firstMobileprovision, new CommandRunner()).getUUID() + ".mobileprovision"; - String secondName = "gradle-" + new ProvisioningProfileReader(secondMobileprovision, new CommandRunner()).getUUID() + ".mobileprovision"; + setup: + project.extensions + .findByType(XcodeBuildPluginExtension) + .signing + .setMobileProvisionURI(testMobileProvision1.toURI().toString(), + testMobileProvision2.toURI().toString()) - File firstSource = new File(projectDir, "build/provision/" + firstName) - File firstDestination = new File(provisionLibraryPath, firstName) + String name1 = formatProvisionFileName(testMobileProvision1) + String name2 = formatProvisionFileName(testMobileProvision2) - File secondSource = new File(projectDir, "build/provision/" + secondName) - File secondDestination = new File(provisionLibraryPath, secondName) + File downloadedFile1 = new File(tmpDirectory.root, "build/provision/" + name1) + File libraryFile1 = new File(ProvisioningInstallTask.PROVISIONING_DIR, name1) - File firstFile = new File(projectDir, "build/provision/" + firstName) - File secondFile = new File(projectDir, "build/provision/" + secondName) + File downloadedFile2 = new File(tmpDirectory.root, "build/provision/" + name2) + File libraryFile2 = new File(ProvisioningInstallTask.PROVISIONING_DIR, name2) when: - provisioningInstallTask.install() - + subject.download() then: - firstFile.exists() - secondFile.exists() - 1 * commandRunner.run(["/bin/ln", "-s", firstSource.absolutePath, firstDestination.absolutePath]) - 1 * commandRunner.run(["/bin/ln", "-s", secondSource.absolutePath, secondDestination.absolutePath]) + noExceptionThrown() + + and: + downloadedFile1.text == testMobileProvision1.text + downloadedFile1.exists() + + libraryFile1.text == testMobileProvision1.text + libraryFile1.exists() + + and: + downloadedFile2.text == testMobileProvision2.text + downloadedFile2.exists() + + libraryFile2.text == testMobileProvision2.text + libraryFile2.exists() } + def "Should process without error multiple provisioning files by using directly the `mobileProvisionList` property"() { - def "mobileProvisionFile has mobileprovision extension"() { - given: - File testMobileprovision = new File("../libtest/src/main/Resource/test.mobileprovision") - project.xcodebuild.signing.mobileProvisionURI = testMobileprovision.toURI().toString() + setup: + project.extensions + .findByType(XcodeBuildPluginExtension) + .signing + .mobileProvisionList.set([testMobileProvision1.toURI().toString(), + testMobileProvision2.toURI().toString()]) - ProvisioningProfileReader provisioningProfileIdReader = new ProvisioningProfileReader(testMobileprovision, commandRunner) - String uuid = provisioningProfileIdReader.getUUID() + String name1 = formatProvisionFileName(testMobileProvision1) + String name2 = formatProvisionFileName(testMobileProvision2) + + File downloadedFile1 = new File(tmpDirectory.root, "build/provision/" + name1) + File libraryFile1 = new File(ProvisioningInstallTask.PROVISIONING_DIR, name1) + + File downloadedFile2 = new File(tmpDirectory.root, "build/provision/" + name2) + File libraryFile2 = new File(ProvisioningInstallTask.PROVISIONING_DIR, name2) when: - provisioningInstallTask.install() + subject.download() then: - project.xcodebuild.signing.mobileProvisionFile.size == 1 - project.xcodebuild.signing.mobileProvisionFile[0].toString().endsWith(uuid + ".mobileprovision") - } + noExceptionThrown() - def "has provisionprofile extension"() { - given: - File testMobileprovision = new File("../plugin/src/test/Resource/test-wildcard-mac.provisionprofile") - project.xcodebuild.signing.mobileProvisionURI = testMobileprovision.toURI().toString() + and: + downloadedFile1.text == testMobileProvision1.text + downloadedFile1.exists() - ProvisioningProfileReader provisioningProfileIdReader = new ProvisioningProfileReader(testMobileprovision, commandRunner) - String uuid = provisioningProfileIdReader.getUUID() + libraryFile1.text == testMobileProvision1.text + libraryFile1.exists() - when: - provisioningInstallTask.install() + and: + downloadedFile2.text == testMobileProvision2.text + downloadedFile2.exists() - then: - project.xcodebuild.signing.mobileProvisionFile.size == 1 - project.xcodebuild.signing.mobileProvisionFile[0].toString().endsWith(uuid + ".provisionprofile") + libraryFile2.text == testMobileProvision2.text + libraryFile2.exists() } } diff --git a/plugin/src/test/groovy/org/openbakery/signing/ProvisioningProfileReaderSpecification.groovy b/plugin/src/test/groovy/org/openbakery/signing/ProvisioningProfileReaderSpecification.groovy index 04be076b..6b7644c7 100644 --- a/plugin/src/test/groovy/org/openbakery/signing/ProvisioningProfileReaderSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/signing/ProvisioningProfileReaderSpecification.groovy @@ -1,53 +1,55 @@ package org.openbakery.signing -import ch.qos.logback.core.util.FileUtil import org.apache.commons.configuration.plist.XMLPropertyListConfiguration import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.openbakery.CommandRunner +import org.openbakery.XcodeBuildPluginExtension +import org.openbakery.XcodePlugin import org.openbakery.codesign.ProvisioningProfileReader -import org.openbakery.configuration.Configuration import org.openbakery.configuration.ConfigurationFromMap import org.openbakery.configuration.ConfigurationFromPlist -import org.openbakery.xcode.Type -import org.openbakery.XcodePlugin -import org.openbakery.packaging.PackageTask +import org.openbakery.packaging.PackageLegacyTask import org.openbakery.util.PlistHelper +import org.openbakery.xcode.Type import spock.lang.Specification import static org.hamcrest.MatcherAssert.assertThat import static org.hamcrest.Matchers.equalTo import static org.hamcrest.Matchers.is - class ProvisioningProfileReaderSpecification extends Specification { Project project - PackageTask packageTask; + PackageLegacyTask packageTask; File projectDir File buildOutputDirectory File appDirectory CommandRunner commandRunner = Mock(CommandRunner) PlistHelper plistHelper + XcodeBuildPluginExtension extension def setup() { projectDir = new File(System.getProperty("java.io.tmpdir"), "gradle-xcodebuild") project = ProjectBuilder.builder().withProjectDir(projectDir).build() project.buildDir = new File(projectDir, 'build').absoluteFile - project.apply plugin: org.openbakery.XcodePlugin + project.apply plugin: XcodePlugin project.xcodebuild.infoPlist = 'Info.plist' project.xcodebuild.productName = 'Example' project.xcodebuild.productType = 'app' project.xcodebuild.type = Type.macOS - project.xcodebuild.signing.keychain = "/var/tmp/gradle.keychain" + project.xcodebuild.signing.keychain.set(project.file("/var/tmp/gradle.keychain")) + + packageTask = project.getTasks().getByPath(PackageLegacyTask.NAME) - packageTask = project.getTasks().getByPath(XcodePlugin.PACKAGE_TASK_NAME) + extension = project.extensions.findByType(XcodeBuildPluginExtension) - buildOutputDirectory = new File(project.xcodebuild.symRoot, project.xcodebuild.configuration) + + buildOutputDirectory = new File(extension.symRoot.asFile.get(), extension.configuration) buildOutputDirectory.mkdirs() appDirectory = new File(buildOutputDirectory, "Example.app") @@ -74,20 +76,20 @@ class ProvisioningProfileReaderSpecification extends Specification { def "read application identifier prefix"() { when: ProvisioningProfileReader reader = new ProvisioningProfileReader(new File("../libtest/src/main/Resource/test.mobileprovision"), new CommandRunner()) + then: reader.getApplicationIdentifierPrefix().equals("AAAAAAAAAAA") } - def "read application identifier"() { when: ProvisioningProfileReader reader = new ProvisioningProfileReader(new File("../libtest/src/main/Resource/test.mobileprovision"), new CommandRunner()) + then: reader.getApplicationIdentifier() == "org.openbakery.test.Example" } - - def "profile has expired" () { + def "profile has expired"() { when: new ProvisioningProfileReader(new File("src/test/Resource/expired.mobileprovision"), new CommandRunner()) @@ -96,19 +98,15 @@ class ProvisioningProfileReaderSpecification extends Specification { } def "read Mac Provisioning Profile"() { - given: File wildcardMacProfile = new File("src/test/Resource/test-wildcard-mac-development.provisionprofile") when: ProvisioningProfileReader provisioningProfileReader = new ProvisioningProfileReaderIgnoreExpired(wildcardMacProfile, new CommandRunner()) - - def applicationIdentifier = provisioningProfileReader.getApplicationIdentifier() + String applicationIdentifier = provisioningProfileReader.getApplicationIdentifier() then: - assertThat(applicationIdentifier, is(equalTo("*"))) - } def "extract Entitlements has nothing to extract"() { @@ -116,19 +114,17 @@ class ProvisioningProfileReaderSpecification extends Specification { ProvisioningProfileReader reader = new ProvisioningProfileReaderIgnoreExpired(provisioningProfile, commandRunner, new PlistHelper(new CommandRunner())) commandRunner.runWithResult([ - "/usr/libexec/PlistBuddy", - "-x", - provisioningProfile.absolutePath, - "-c", - "Print Entitlements"]) >> null + "/usr/libexec/PlistBuddy", + "-x", + provisioningProfile.absolutePath, + "-c", + "Print Entitlements"]) >> null File entitlementsFile = new File(projectDir, "entitlements.plist") expect: // no exception should be thrown! reader.extractEntitlements(entitlementsFile, "org.openbakery.test.Example", null, null) - - } def "extract Entitlements"() { @@ -186,7 +182,6 @@ class ProvisioningProfileReaderSpecification extends Specification { entitlements.getString("com..apple..application-identifier") == "Z7L2YCUH45.org.openbakery.test.Example" } - def "extract Entitlements with keychain access group"() { given: String expectedContents = "\n" + @@ -262,7 +257,6 @@ class ProvisioningProfileReaderSpecification extends Specification { entitlements.getList("keychain-access-groups").contains("AAAAAAAAAA.com.example.Test") } - def "extract Entitlements test application identifier"() { given: File mobileprovision = new File("src/test/Resource/openbakery.mobileprovision") @@ -287,28 +281,27 @@ class ProvisioningProfileReaderSpecification extends Specification { entitlementsFile.text.contains("CCCCCCCCCC.com.example.Test") } - String getEntitlementWithApplicationIdentifier(String applicationIdentifier) { return "\n" + - "\n" + - "\n" + - "\n" + - " keychain-access-groups\n" + - " \n" + - " AAAAAAAAAA.*\n" + - " \n" + - " get-task-allow\n" + - " \n" + - " application-identifier\n" + - " " + applicationIdentifier + "\n" + - " com.apple.developer.team-identifier\n" + - " AAAAAAAAAA\n" + - " com.apple.developer.ubiquity-kvstore-identifier\n" + - " ABCDE12345.*\n" + - " com.apple.developer.ubiquity-container-identifiers\n" + - " ABCDE12345.*\n" + - "\n" + - "" + "\n" + + "\n" + + "\n" + + " keychain-access-groups\n" + + " \n" + + " AAAAAAAAAA.*\n" + + " \n" + + " get-task-allow\n" + + " \n" + + " application-identifier\n" + + " " + applicationIdentifier + "\n" + + " com.apple.developer.team-identifier\n" + + " AAAAAAAAAA\n" + + " com.apple.developer.ubiquity-kvstore-identifier\n" + + " ABCDE12345.*\n" + + " com.apple.developer.ubiquity-container-identifiers\n" + + " ABCDE12345.*\n" + + "\n" + + "" } def "extract Entitlements with wildcard application identifier that does not match"() { @@ -321,7 +314,7 @@ class ProvisioningProfileReaderSpecification extends Specification { ProvisioningProfileReader reader = new ProvisioningProfileReader(mobileprovision, commandRunner) def keychainAccessGroups = [ - ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", + ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", ] File entitlementsFile = new File(projectDir, "entitlements.plist") @@ -331,7 +324,6 @@ class ProvisioningProfileReaderSpecification extends Specification { thrown(IllegalStateException.class) } - def "extract Entitlements with wildcard application identifier"() { given: File mobileprovision = new File("src/test/Resource/openbakery.mobileprovision") @@ -342,7 +334,7 @@ class ProvisioningProfileReaderSpecification extends Specification { ProvisioningProfileReader reader = new ProvisioningProfileReader(mobileprovision, commandRunner, new PlistHelper(new CommandRunner())) def keychainAccessGroups = [ - ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", + ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", ] File entitlementsFile = new File(projectDir, "entitlements.plist") @@ -355,8 +347,6 @@ class ProvisioningProfileReaderSpecification extends Specification { } - - def "extract Entitlements with wildcard application identifier that does match"() { given: File mobileprovision = new File("src/test/Resource/openbakery.mobileprovision") @@ -367,7 +357,7 @@ class ProvisioningProfileReaderSpecification extends Specification { ProvisioningProfileReader reader = new ProvisioningProfileReader(mobileprovision, commandRunner, new PlistHelper(new CommandRunner())) def keychainAccessGroups = [ - ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", + ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", ] File entitlementsFile = new File(projectDir, "entitlements.plist") @@ -378,7 +368,6 @@ class ProvisioningProfileReaderSpecification extends Specification { entitlementsFile.text.contains("AAAAAAAAAAA.org.openbakery.test.Example.widget") } - def "is ad-hoc profile"() { when: ProvisioningProfileReader reader = new ProvisioningProfileReader(new File("../libtest/src/main/Resource/test.mobileprovision"), new CommandRunner()) @@ -387,7 +376,6 @@ class ProvisioningProfileReaderSpecification extends Specification { reader.isAdHoc() == true } - def "is not ad-hoc profile"() { when: ProvisioningProfileReader reader = new ProvisioningProfileReader(new File("../libtest/src/main/Resource/Appstore.mobileprovision"), new CommandRunner()) @@ -396,7 +384,6 @@ class ProvisioningProfileReaderSpecification extends Specification { reader.isAdHoc() == false } - def "extract Entitlements with wildcard and kvstore should start with team id"() { given: File mobileprovision = new File("src/test/Resource/openbakery-team.mobileprovision") @@ -407,7 +394,7 @@ class ProvisioningProfileReaderSpecification extends Specification { ProvisioningProfileReader reader = new ProvisioningProfileReader(mobileprovision, commandRunner, new PlistHelper(new CommandRunner())) def keychainAccessGroups = [ - ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", + ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", ] File entitlementsFile = new File(projectDir, "entitlements.plist") @@ -428,7 +415,7 @@ class ProvisioningProfileReaderSpecification extends Specification { ProvisioningProfileReader reader = new ProvisioningProfileReader(mobileprovision, commandRunner, new PlistHelper(new CommandRunner())) def keychainAccessGroups = [ - ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", + ProvisioningProfileReader.APPLICATION_IDENTIFIER_PREFIX + "org.openbakery.test.Example", ] File entitlementsFile = new File(projectDir, "entitlements.plist") @@ -439,7 +426,6 @@ class ProvisioningProfileReaderSpecification extends Specification { plistHelper.getValueFromPlist(entitlementsFile, "com.apple.developer.ubiquity-container-identifiers").startsWith("XXXXXZZZZZ.") } - def "get provisioning profile from plist"() { def commandList @@ -453,12 +439,11 @@ class ProvisioningProfileReaderSpecification extends Specification { then: 1 * commandRunner.run(_) >> { arguments -> commandList = arguments[0] } - commandList == ["security","cms","-D","-i", mobileprovision.absolutePath, "-o", expectedProvisioningPlist.absolutePath] + commandList == ["security", "cms", "-D", "-i", mobileprovision.absolutePath, "-o", expectedProvisioningPlist.absolutePath] } - def "provisioning match"() { given: File appMobileprovision = new File("../libtest/src/main/Resource/test.mobileprovision") @@ -467,9 +452,9 @@ class ProvisioningProfileReaderSpecification extends Specification { when: def list = [ - appMobileprovision, - widgetMobileprovision, - wildcardMobileprovision + appMobileprovision, + widgetMobileprovision, + wildcardMobileprovision ] then: @@ -480,7 +465,6 @@ class ProvisioningProfileReaderSpecification extends Specification { } - def "provisioning Match more"() { given: File appMobileprovision = new File("src/test/Resource/openbakery.mobileprovision") @@ -488,8 +472,8 @@ class ProvisioningProfileReaderSpecification extends Specification { when: def list = [ - appMobileprovision, - wildcardMobileprovision + appMobileprovision, + wildcardMobileprovision ] then: @@ -499,7 +483,6 @@ class ProvisioningProfileReaderSpecification extends Specification { } - def "extract Entitlements and merge Example.entitlements"() { given: File mobileprovision = new File("src/test/Resource/openbakery.mobileprovision") @@ -523,7 +506,6 @@ class ProvisioningProfileReaderSpecification extends Specification { } - def setupForEntitlementTest(Map data) { File mobileprovision = new File("src/test/Resource/openbakery.mobileprovision") @@ -539,7 +521,6 @@ class ProvisioningProfileReaderSpecification extends Specification { return destinationFile } - def "extract Entitlements and replace com.apple.developer.icloud-container-identifiers"() { given: Map data = ["com.apple.developer.icloud-container-identifiers": ["iCloud.com.example.Test"]] @@ -553,7 +534,6 @@ class ProvisioningProfileReaderSpecification extends Specification { entitlements.getList("com..apple..developer..icloud-container-identifiers").contains("iCloud.com.example.Test") } - def "extract Entitlements and replace ubiquity-container-identifiers"() { given: Map data = ["com.apple.developer.ubiquity-container-identifiers": ["com.example.Test"]] @@ -623,11 +603,10 @@ class ProvisioningProfileReaderSpecification extends Specification { entitlements.getString("com..apple..developer..icloud-services") == "com.example.test" } - def "extract Entitlements set keychain access group with configuration key and replace AppIdentiferPrefix variable"() { given: Map data = [ - "keychain-access-groups": [ "\$(AppIdentifierPrefix)com.example.Test" ] + "keychain-access-groups": ["\$(AppIdentifierPrefix)com.example.Test"] ] File entitlementsFile = setupForEntitlementTest(data) @@ -640,7 +619,5 @@ class ProvisioningProfileReaderSpecification extends Specification { entitlements.getList("keychain-access-groups").contains("AAAAAAAAAAA.com.example.Test") } - - } diff --git a/plugin/src/test/groovy/org/openbakery/signing/SigningSpecification.groovy b/plugin/src/test/groovy/org/openbakery/signing/SigningSpecification.groovy index 9ad5759e..66dbb2db 100644 --- a/plugin/src/test/groovy/org/openbakery/signing/SigningSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/signing/SigningSpecification.groovy @@ -1,142 +1,228 @@ package org.openbakery.signing -import org.apache.commons.io.FileUtils import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder +import org.junit.Rule +import org.junit.rules.ExpectedException +import org.junit.rules.TemporaryFolder +import org.openbakery.CommandRunner +import org.openbakery.XcodeBuildPluginExtension +import org.openbakery.XcodePlugin import org.openbakery.codesign.CodesignParameters import org.openbakery.configuration.ConfigurationFromMap +import org.openbakery.extension.Signing import spock.lang.Specification +import spock.lang.Unroll class SigningSpecification extends Specification { - Signing signing - Project project - File projectDir + Signing signing + Project project + File projectDir + + @Rule + final TemporaryFolder testProjectDir = new TemporaryFolder() + + @Rule + public ExpectedException exception = ExpectedException.none() + + private Signing signingExtension + + def setup() { + projectDir = testProjectDir.root + + project = ProjectBuilder.builder().withProjectDir(projectDir).build() + project.buildDir = new File(projectDir, 'build').absoluteFile + project.apply plugin: XcodePlugin + + signing = new Signing(project, new CommandRunner()) + this.signingExtension = project.extensions + .findByType(XcodeBuildPluginExtension) + .signing + } - def setup() { - projectDir = new File(System.getProperty("java.io.tmpdir"), "gradle-xcodebuild") + def cleanup() { + projectDir.delete() + } - project = ProjectBuilder.builder().withProjectDir(projectDir).build() - project.buildDir = new File(projectDir, 'build').absoluteFile - project.apply plugin: org.openbakery.XcodePlugin - signing = new Signing(project) - } + def "has identity"() { + when: + signing.identity = "Me!" + then: + signing.identity == "Me!" + } + @Unroll + def "the signing method can be resolved from a valid string value for method: #method"() { + when: + signing.setMethod(string) + + then: + noExceptionThrown() + signing.signingMethod.get() == method - def cleanup() { - projectDir.delete() - } + where: + string | method + "ad-hoc" | SigningMethod.AdHoc + "app-store" | SigningMethod.AppStore + "development" | SigningMethod.Dev + "enterprise" | SigningMethod.Enterprise + } - def "has identity"() { - when: - signing.identity = "Me!" - then: - signing.identity == "Me!" - } + def "Should throw an expection when trying to parse an invalid signature method"() { + when: + signing.setMethod(string) - def "entitlements data set via closure using xcodebuild"() { + then: + thrown(IllegalArgumentException.class) - when: - project.xcodebuild.signing { - entitlements 'com.apple.security.application-groups': ['group.com.example.App'] - } + where: + string | _ + "ad-hc" | _ + "app-stre" | _ + null | _ + } + + def "The XcConfig file path can be configured and is by default buildDir dependant"() { + when: + File file = signing.xcConfigFile.asFile.get() + + then: + noExceptionThrown() + file.absoluteFile == new File(project.buildDir, "archive/archive.xcconfig") - then: - project.xcodebuild.signing.entitlements instanceof Map - project.xcodebuild.signing.entitlements.containsKey("com.apple.security.application-groups") + when: + File altBuildDir = testProjectDir.newFolder("build-dir-alt") + assert project.buildDir != altBuildDir + project.buildDir = altBuildDir - } + and: + File file2 = signing.xcConfigFile.asFile.get() - def "entitlements data set via closure"() { - when: - signing.entitlements('com.apple.security.application-groups': ['group.com.example.App']) + then: + noExceptionThrown() + file2.absoluteFile == new File(altBuildDir, "archive/archive.xcconfig") + } - then: - signing.entitlements instanceof Map - signing.entitlements.containsKey("com.apple.security.application-groups") - signing.entitlements["com.apple.security.application-groups"] == ['group.com.example.App'] - } + def "entitlements data set via closure using xcodebuild"() { - - def "entitlements data set via closure converted to Configuration"() { - when: - signing.entitlements('com.apple.security.application-groups': ['group.com.example.App']) - - def configuration = new ConfigurationFromMap(signing.entitlements) - - then: - configuration.getStringArray("com.apple.security.application-groups") == ['group.com.example.App'] - - } - - def "codesignParameters is not null"() { - when: - signing.identity = "Me" - - then: - signing.codesignParameters instanceof CodesignParameters - } - - def "codesignParameters has identity"() { - when: - signing.identity = "Me" - then: - signing.codesignParameters.signingIdentity == "Me" - } - - def "codesignParameters has mobileProvisionFiles"() { - when: - - File first = new File(projectDir, "first") - FileUtils.write(first, "first") - File second = new File(projectDir, "second") - FileUtils.write(second, "second") - - signing.addMobileProvisionFile(first) - signing.addMobileProvisionFile(second) - - then: - signing.codesignParameters.mobileProvisionFiles instanceof List - signing.codesignParameters.mobileProvisionFiles == [first, second] - } - - def "codesignParameters has keychain"() { - when: - signing.keychain = new File("my.keychain").absoluteFile - then: - signing.codesignParameters.keychain == new File("my.keychain").absoluteFile - } - - - def "codesignParameters has entitlements"() { - when: - signing.entitlements = ['key': 'value'] - then: - signing.codesignParameters.entitlements == ['key': 'value'] - } - - def "codesignParameters has entitlementsFile"() { - when: - signing.entitlementsFile = new File("entitlements") - then: - signing.codesignParameters.entitlementsFile == new File("entitlements") - } - - - def "codesignParameter entitlementsFile as String"() { - when: - signing.entitlementsFile = "entitlements" - then: - signing.codesignParameters.entitlementsFile instanceof File - signing.codesignParameters.entitlementsFile.path.endsWith("entitlements") - } - - def "codesignParameter entitlementsFile as String full path"() { - when: - signing.entitlementsFile = "file:///entitlements" - then: - signing.codesignParameters.entitlementsFile instanceof File - signing.codesignParameters.entitlementsFile.path.endsWith("entitlements") - } + when: + assert signingExtension != null + signingExtension.entitlements 'com.apple.security.application-groups': ['group.com.example.App'] + + then: + signingExtension.entitlementsMap.get() instanceof Map + signingExtension.entitlementsMap.get().containsKey("com.apple.security.application-groups") + } + + def "entitlements data set via closure"() { + when: + assert signingExtension != null + signingExtension.entitlements('com.apple.security.application-groups': ['group.com.example.App']) + + then: + signingExtension.entitlementsMap.get() instanceof Map + signingExtension.entitlementsMap.get().containsKey("com.apple.security.application-groups") + signingExtension.entitlementsMap.get()["com.apple.security.application-groups"] == ['group.com.example.App'] + } + + def "entitlements data set via closure converted to Configuration"() { + when: + signing.entitlements('com.apple.security.application-groups': ['group.com.example.App']) + + def configuration = new ConfigurationFromMap(signing.entitlementsMap.get()) + + then: + configuration.getStringArray("com.apple.security.application-groups") == ['group.com.example.App'] + } + + def "codesignParameters is not null"() { + when: + signing.identity = "Me" + + then: + signing.codesignParameters instanceof CodesignParameters + } + + def "codesignParameters has identity"() { + when: + signing.identity = "Me" + + then: + signing.codesignParameters.signingIdentity == "Me" + } + + def "codesignParameters has keychain"() { + when: + signing.keychain = new File("my.keychain").absoluteFile + then: + signing.codesignParameters.keychain == new File("my.keychain").absoluteFile + } + + + def "codesignParameters has entitlements"() { + when: + signing.entitlements(['key': 'value']) + + then: + signing.codesignParameters.entitlements == ['key': 'value'] + } + + def "codesignParameters has entitlementsFile"() { + setup: + File entitlementsFile = testProjectDir.newFile("test.entitlements") + + when: + signing.entitlementsFile = entitlementsFile + + then: + signing.codesignParameters.entitlementsFile == entitlementsFile + } + + def "When defining the certificate the friendlyName should be updated"() { + setup: + File cert = new File("src/test/Resource/fake_distribution.p12") + assert cert.exists() + signing.certificate.set(cert.absoluteFile) + + when: "If not password is defined, should trow an exception" + signing.certificateFriendlyName.get() + + then: + thrown IllegalStateException.class + + when: "Defining password" + signing.certificatePassword.set("p4ssword") + + and: + signing.certificateFriendlyName.get() == "iPhone Distribution: Test Company Name (12345ABCDE)" + + then: + noExceptionThrown() + } + + def "codesignParameter entitlementsFile as String"() { + when: + signing.entitlementsFile = "entitlements/test.entitlements" + + then: + noExceptionThrown() + with(signing.entitlementsFile + .get() + .asFile) { + name == "test.entitlements" + parent.endsWith("/entitlements") + } + } + + def "codesignParameter entitlementsFile as String full path"() { + when: + signing.entitlementsFile = "file:///entitlements" + + then: + noExceptionThrown() + signing.codesignParameters.entitlementsFile instanceof File + signing.codesignParameters.entitlementsFile.path.endsWith("entitlements") + } } diff --git a/plugin/src/test/groovy/org/openbakery/simulators/SimulatorInstallAppTaskSpecification.groovy b/plugin/src/test/groovy/org/openbakery/simulators/SimulatorInstallAppTaskSpecification.groovy index 9e675274..37a4c610 100644 --- a/plugin/src/test/groovy/org/openbakery/simulators/SimulatorInstallAppTaskSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/simulators/SimulatorInstallAppTaskSpecification.groovy @@ -7,25 +7,20 @@ import org.openbakery.codesign.Codesign import org.openbakery.xcode.Type import spock.lang.Specification -/** - * Created by rene on 27.03.17. - */ class SimulatorInstallAppTaskSpecification extends Specification { - SimulatorInstallAppTask task Project project File projectDir def setup() { - projectDir = new File(System.getProperty("java.io.tmpdir"), "gradle-xcodebuild") project = ProjectBuilder.builder().withProjectDir(projectDir).build() - project.apply plugin: org.openbakery.XcodePlugin + project.apply plugin: XcodePlugin task = project.tasks.findByName(XcodePlugin.SIMULATORS_INSTALL_APP_TASK_NAME) - } + def "create"() { expect: task instanceof SimulatorInstallAppTask @@ -34,7 +29,8 @@ class SimulatorInstallAppTaskSpecification extends Specification { def "depends on"() { when: - def dependsOn = task.getDependsOn() + def dependsOn = task.getDependsOn() + then: dependsOn.size() == 2 dependsOn.contains(XcodePlugin.XCODE_BUILD_TASK_NAME) @@ -53,7 +49,9 @@ class SimulatorInstallAppTaskSpecification extends Specification { task.run() then: - 1* simulatorControl.simctl(["install", "booted", project.xcodebuild.applicationBundle.absolutePath]) + 1 * simulatorControl.simctl(["install", + "booted", + project.xcodebuild.applicationBundle.absolutePath]) } def "codesign is not null"() { @@ -77,8 +75,7 @@ class SimulatorInstallAppTaskSpecification extends Specification { Codesign codesign = Mock(Codesign) task.codesign = codesign - SimulatorControl simulatorControl = Mock(SimulatorControl) - task.simulatorControl = simulatorControl + task.simulatorControl = Mock(SimulatorControl) project.xcodebuild.bundleName = "MyApp" @@ -88,7 +85,4 @@ class SimulatorInstallAppTaskSpecification extends Specification { then: 1 * codesign.sign(project.xcodebuild.applicationBundle) } - - - } diff --git a/plugin/src/test/groovy/org/openbakery/util/SystemUtilTest.groovy b/plugin/src/test/groovy/org/openbakery/util/SystemUtilTest.groovy new file mode 100644 index 00000000..48287c9d --- /dev/null +++ b/plugin/src/test/groovy/org/openbakery/util/SystemUtilTest.groovy @@ -0,0 +1,28 @@ +package org.openbakery.util + +import org.openbakery.xcode.Version +import spock.lang.Specification + +class SystemUtilTest extends Specification { + def "Should properly resolve system version from system property"() { + + setup: + System.setProperty("os.version", systemVersion); + + when: + Version version = SystemUtil.getOsVersion() + + then: + version.major == major + version.minor == minor + version.maintenance == maintenance + + where: + systemVersion | major | minor | maintenance + "0.0" | 0 | 0 | -1 + "10.3" | 10 | 3 | -1 + "10.0.1" | 10 | 0 | 1 + "10.1.1" | 10 | 1 | 1 + "10.8.1" | 10 | 8 | 1 + } +} diff --git a/plugin/src/test/groovy/org/openbakery/xcode/DestinationResolverSpecification.groovy b/plugin/src/test/groovy/org/openbakery/xcode/DestinationResolverSpecification.groovy index 948d0704..f1410eac 100644 --- a/plugin/src/test/groovy/org/openbakery/xcode/DestinationResolverSpecification.groovy +++ b/plugin/src/test/groovy/org/openbakery/xcode/DestinationResolverSpecification.groovy @@ -2,13 +2,24 @@ package org.openbakery.xcode import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder +import org.junit.Rule +import org.junit.rules.ExpectedException +import org.junit.rules.TemporaryFolder +import org.openbakery.CommandRunner import org.openbakery.XcodeBuildPluginExtension +import org.openbakery.XcodePlugin import org.openbakery.simulators.SimulatorControl import org.openbakery.testdouble.SimulatorControlStub import spock.lang.Specification class DestinationResolverSpecification extends Specification { + @Rule + public ExpectedException exception = ExpectedException.none() + + @Rule + final TemporaryFolder testProjectDir = new TemporaryFolder() + Project project File projectDir XcodeBuildPluginExtension extension @@ -16,18 +27,14 @@ class DestinationResolverSpecification extends Specification { SimulatorControl simulatorControl def setup() { - projectDir = new File(System.getProperty("java.io.tmpdir"), "gradle-xcodebuild") - project = ProjectBuilder.builder().withProjectDir(projectDir).build() - project.apply plugin: org.openbakery.XcodePlugin - extension = new XcodeBuildPluginExtension(project) + project = ProjectBuilder.builder().withProjectDir(testProjectDir.root).build() + project.apply plugin: XcodePlugin + extension = new XcodeBuildPluginExtension(project, new CommandRunner()) simulatorControl = new SimulatorControlStub("simctl-list-xcode7_1.txt") destinationResolver = new DestinationResolver(simulatorControl) } - - def "available destinations for OS X"() { - when: extension.type = Type.macOS @@ -35,11 +42,10 @@ class DestinationResolverSpecification extends Specification { destinationResolver.getDestinations(extension.getXcodebuildParameters()).size() == 1 } - def "XcodebuildParameters are created with iOS destination"() { when: Project project = ProjectBuilder.builder().build() - extension = new XcodeBuildPluginExtension(project) + extension = new XcodeBuildPluginExtension(project, new CommandRunner()) extension.type = Type.iOS extension.destination = ['iPad 2'] @@ -47,13 +53,10 @@ class DestinationResolverSpecification extends Specification { def destinations = destinationResolver.getDestinations(parameters) then: - destinations.size() == 1 destinations[0].name == "iPad 2" - } - def "test configured devices only should add most recent runtime"() { when: extension.destination = ['iPad 2'] @@ -66,7 +69,6 @@ class DestinationResolverSpecification extends Specification { destinations[0].os == "9.1" } - def "available destinations default xcode 7"() { when: destinationResolver.simulatorControl = new SimulatorControlStub("simctl-list-xcode7.txt"); @@ -77,7 +79,6 @@ class DestinationResolverSpecification extends Specification { } - def "available destinations default"() { when: @@ -85,7 +86,6 @@ class DestinationResolverSpecification extends Specification { then: destinations.size() == 22 - } def "available destinations default for 9.1 SDK"() { @@ -94,7 +94,6 @@ class DestinationResolverSpecification extends Specification { } when: - def destinations = destinationResolver.getDestinations(extension.getXcodebuildParameters()) then: @@ -102,9 +101,7 @@ class DestinationResolverSpecification extends Specification { } - def "available destinations match"() { - extension.destination { platform = 'iOS Simulator' name = 'iPad Air' @@ -116,10 +113,8 @@ class DestinationResolverSpecification extends Specification { then: destinations.size() == 1 - } - def "available destinations not match"() { given: extension.destination { @@ -136,7 +131,6 @@ class DestinationResolverSpecification extends Specification { } - def "available destinations match simple single"() { given: extension.destination = 'iPad Air' @@ -148,7 +142,6 @@ class DestinationResolverSpecification extends Specification { destinations.size() == 1 destinations[0].name == "iPad Air" destinations[0].os == "9.1" - } def "available destinations match simple multiple"() { @@ -161,10 +154,8 @@ class DestinationResolverSpecification extends Specification { then: destinations.size() == 2 - } - def "set destinations twice"() { given: @@ -177,7 +168,6 @@ class DestinationResolverSpecification extends Specification { then: destinations.size() == 2 destinations[1].name == 'iPhone 4s' - } def "resolves tvOS destination from the name"() { @@ -205,7 +195,6 @@ class DestinationResolverSpecification extends Specification { destinations[0].name == "Apple TV 1080p" } - def "resolve iPad Pro (12.9 inch)"() { given: simulatorControl = new SimulatorControlStub("simctl-list-xcode8.txt") @@ -219,8 +208,5 @@ class DestinationResolverSpecification extends Specification { destinations.size() == 1 destinations[0].name == 'iPad Pro (12.9 inch)' destinations[0].id == 'C538D7F8-E581-44FF-9B17-5391F84642FB' - - } - }