From f8720860993ddf9816a315fad5965d1e9b3b42a7 Mon Sep 17 00:00:00 2001 From: Bruno Bowden Date: Mon, 19 Oct 2015 22:34:01 -0700 Subject: [PATCH] Minimum OS versions, fixed iOS & OS X builds - Minimum OS versions for iOS, OS X and watchOS - xcodeTargets => xcodeTargetsIos, xcodeTargetsOsx & xcodeTargetsWatchos - Extensive refactoring of Podfile updating logic - Extensive unit tests for all the forgoing changes - Fixes #520 TODO: get watchOS build working --- .../j2objcgradle/J2objcConfig.groovy | 57 +- .../j2objcgradle/NativeCompilation.groovy | 4 +- .../j2objcgradle/tasks/PodspecTask.groovy | 16 +- .../j2objcgradle/tasks/XcodeTask.groovy | 409 +++++++----- .../j2objcgradle/tasks/XcodeTaskTest.groovy | 629 +++++++++++++----- 5 files changed, 769 insertions(+), 346 deletions(-) diff --git a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy index 2f381748..31687549 100644 --- a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy @@ -622,7 +622,8 @@ class J2objcConfig { *

* See https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/cross_development/Configuring/configuring.html#//apple_ref/doc/uid/10000163i-CH1-SW2 */ - String minIosVersion = '6.0' + // Matches the oldest version supported in Xcode 7 + String minVersionIos = '6.0' /** * The minimum OS X version to build against. You cannot use APIs that are not supported @@ -630,7 +631,8 @@ class J2objcConfig { *

* See https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/cross_development/Configuring/configuring.html#//apple_ref/doc/uid/10000163i-CH1-SW2 */ - String minOsxVersion = '10.4' + // Matches the oldest version supported in Xcode 7 + String minVersionOsx = '10.4' /** * The minimum Watch OS version to build against. You cannot use APIs that are not supported @@ -638,7 +640,8 @@ class J2objcConfig { *

* See https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/cross_development/Configuring/configuring.html#//apple_ref/doc/uid/10000163i-CH1-SW2 */ - String minWatchosVersion = '1.0' + // Matches the oldest version supported in Xcode 7 + String minVersionWatchos = '1.0' // XCODE /** @@ -649,28 +652,54 @@ class J2objcConfig { * */ String xcodeProjectDir = null + /** - * Xcode app targets that should be linked to the generated library. + * iOS app and test Xcode targets to link to the generated libraries. * * This will automatically add linkage for any target in the specified list - * to the generated shared library. This should include any Test and Watch - * targets if needed. + * to the generated shared libraries. This should include test targets also. + */ + List xcodeTargetsIos = new ArrayList<>() + /** + * iOS app and test Xcode targets to link to the generated libraries. * - * If empty (default), it will link all targets defined within the Xcode project. + * @param xcodeTargetsIos targets to link to the generated libraries. */ - List xcodeTargets = new ArrayList<>() + void xcodeTargetsIos(String... xcodeTargetsIos) { + appendArgs(this.xcodeTargetsIos, 'xcodeTargetsIos', xcodeTargetsIos) + } + /** - * Add targets within Xcode to link to the generated shared library. + * OS X app and test Xcode targets that should be linked to the generated libraries. * - * If this is never called, it will default to linking all targets - * to the generated shared library. + * This will automatically add linkage for any target in the specified list + * to the generated shared libraries. This should include test targets also. + */ + List xcodeTargetsOsx = new ArrayList<>() + /** + * OS X app and test Xcode targets to link to the generated libraries. * - * @param xcodeTargets links targets to generated library. + * @param xcodeTargetsOsx targets to link to the generated libraries. */ - void xcodeTargets(String... xcodeTargets) { - appendArgs(this.xcodeTargets, 'xcodeTargets', xcodeTargets) + void xcodeTargetsOsx(String... xcodeTargetsOsx) { + appendArgs(this.xcodeTargetsOsx, 'xcodeTargetsOsx', xcodeTargetsOsx) } + /** + * watchOS app and test Xcode targets that should be linked to the generated libraries. + * + * This will automatically add linkage for any target in the specified list + * to the generated shared libraries. This should include test targets also. + */ + List xcodeTargetsWatchos = new ArrayList<>() + /** + * watchOS app and test Xcode targets to link to the generated libraries. + * + * @param xcodeTargetsWatchos targets to link to the generated libraries. + */ + void xcodeTargetsWatchos(String... xcodeTargetsWatchos) { + appendArgs(this.xcodeTargetsWatchos, 'xcodeTargetsWatchos', xcodeTargetsWatchos) + } protected boolean finalConfigured = false diff --git a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/NativeCompilation.groovy b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/NativeCompilation.groovy index 2350b96c..3196719c 100644 --- a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/NativeCompilation.groovy +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/NativeCompilation.groovy @@ -87,12 +87,12 @@ class NativeCompilation { switch (targetSpec) { case TargetSpec.TARGET_IOS_DEVICE: clangArgs += iphoneClangArgs - clangArgs += ["-miphoneos-version-min=${config.minIosVersion}"] + clangArgs += ["-miphoneos-version-min=${config.minVersionIos}"] linkerArgs += ["-L$j2objcPath/lib"] break case TargetSpec.TARGET_IOS_SIMULATOR: clangArgs += simulatorClangArgs - clangArgs += ["-mios-simulator-version-min=${config.minIosVersion}"] + clangArgs += ["-mios-simulator-version-min=${config.minVersionIos}"] linkerArgs += ["-L$j2objcPath/lib"] break case TargetSpec.TARGET_OSX: diff --git a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/PodspecTask.groovy b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/PodspecTask.groovy index 5ced6566..1452d2eb 100644 --- a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/PodspecTask.groovy +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/PodspecTask.groovy @@ -68,11 +68,11 @@ class PodspecTask extends DefaultTask { String getPodNameRelease() { "j2objc-${project.name}-release" } @Input - String getMinIosVersion() { return J2objcConfig.from(project).getMinIosVersion() } + String getMinVersionIos() { return J2objcConfig.from(project).getMinVersionIos() } @Input - String getMinOsxVersion() { return J2objcConfig.from(project).getMinOsxVersion() } + String getMinVersionOsx() { return J2objcConfig.from(project).getMinVersionOsx() } @Input - String getMinWatchosVersion() { return J2objcConfig.from(project).getMinWatchosVersion() } + String getMinVersionWatchos() { return J2objcConfig.from(project).getMinVersionWatchos() } // CocoaPods podspec files that are referenced by the Podfile @@ -97,19 +97,19 @@ class PodspecTask extends DefaultTask { String libDirOsxDebug = relativizeToBuildDir(new File(getDestLibDirFile(), 'x86_64Debug'), project) String libDirOsxRelease = relativizeToBuildDir(new File(getDestLibDirFile(), 'x86_64Release'), project) - validateNumericVersion(getMinIosVersion(), 'minIosVersion') - validateNumericVersion(getMinOsxVersion(), 'minOsxVersion') - validateNumericVersion(getMinWatchosVersion(), 'minWatchosVersion') + validateNumericVersion(getMinVersionIos(), 'minVersionIos') + validateNumericVersion(getMinVersionOsx(), 'minVersionOsx') + validateNumericVersion(getMinVersionWatchos(), 'minVersionWatchos') String podspecContentsDebug = genPodspec(getPodNameDebug(), headerIncludePath, resourceIncludePath, libDirIosDebug, libDirOsxDebug, libDirIosDebug, - getMinIosVersion(), getMinOsxVersion(), getMinWatchosVersion(), + getMinVersionIos(), getMinVersionOsx(), getMinVersionWatchos(), getLibName(), getJ2objcHome()) String podspecContentsRelease = genPodspec(getPodNameRelease(), headerIncludePath, resourceIncludePath, libDirIosRelease, libDirOsxRelease, libDirIosRelease, - getMinIosVersion(), getMinOsxVersion(), getMinWatchosVersion(), + getMinVersionIos(), getMinVersionOsx(), getMinVersionWatchos(), getLibName(), getJ2objcHome()) logger.debug("Writing debug podspec... ${getPodspecDebug()}") diff --git a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTask.groovy b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTask.groovy index 2a3f77c4..922719c1 100644 --- a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTask.groovy +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTask.groovy @@ -44,10 +44,28 @@ import java.util.regex.Matcher @CompileStatic class XcodeTask extends DefaultTask { + + public static final String targetStartRegex = /^\s*target\s+'([^']*)'\s+do\s*$/ + public static final String targetNamedRegex = /^\s*target\s+'TARGET'\s+do\s*$/ + public static final String podMethodStartRegex = /^\s*((def\s*j2objc_)|(# J2ObjC Gradle Plugin)).*/ + public static final String endRegex = /^\s*end\s*/ + @Input @Optional String getXcodeProjectDir() { return J2objcConfig.from(project).xcodeProjectDir } - @Input @Optional - List getXcodeTargets() { return J2objcConfig.from(project).xcodeTargets } + + @Input + List getXcodeTargetsIos() { return J2objcConfig.from(project).xcodeTargetsIos } + @Input + List getXcodeTargetsOsx() { return J2objcConfig.from(project).xcodeTargetsOsx } + @Input + List getXcodeTargetsWatchos() { return J2objcConfig.from(project).xcodeTargetsWatchos } + + @Input + String getMinVersionIos() { return J2objcConfig.from(project).minVersionIos } + @Input + String getMinVersionOsx() { return J2objcConfig.from(project).minVersionOsx } + @Input + String getMinVersionWatchos() { return J2objcConfig.from(project).minVersionWatchos } @OutputFile File getPodfileFile() { @@ -56,21 +74,41 @@ class XcodeTask extends DefaultTask { } static class PodspecDetails { + String projectName + File podspecDebug + File podspecRelease + PodspecDetails(String projectNameIn, File podspecDebugIn, File podspecReleaseIn) { projectName = projectNameIn podspecDebug = podspecDebugIn podspecRelease = podspecReleaseIn } - String projectName - File podspecDebug - File podspecRelease - String getPodMethodName() { return "j2objc_$projectName" } } + static class XcodeTargetDetails { + List xcodeTargetsIos + List xcodeTargetsOsx + List xcodeTargetsWatchos + String minVersionIos + String minVersionOsx + String minVersionWatchos + + XcodeTargetDetails( + List xcodeTargetsIosIn, List xcodeTargetsOsxIn, List xcodeTargetsWatchosIn, + String minVersionIosIn, String minVersionOsxIn, String minVersionWatchosIn) { + xcodeTargetsIos = xcodeTargetsIosIn + xcodeTargetsOsx = xcodeTargetsOsxIn + xcodeTargetsWatchos = xcodeTargetsWatchosIn + minVersionIos = minVersionIosIn + minVersionOsx = minVersionOsxIn + minVersionWatchos = minVersionWatchosIn + } + } + @TaskAction void xcodeConfig() { @@ -106,7 +144,12 @@ class XcodeTask extends DefaultTask { // Write Podfile based on all the podspecs from dependent projects List podspecDetailsList = getPodspecsFromProject(getProject(), new HashSet()) - writeUpdatedPodfileIfNeeded(podspecDetailsList, getXcodeTargets(), podfile, logger) + + XcodeTargetDetails xcodeTargetDetails = new XcodeTargetDetails( + getXcodeTargetsIos(), getXcodeTargetsOsx(), getXcodeTargetsWatchos(), + getMinVersionIos(), getMinVersionOsx(), getMinVersionWatchos()) + + writeUpdatedPodfileIfNeeded(podspecDetailsList, xcodeTargetDetails, podfile, logger) // install the pod ByteArrayOutputStream stdout = new ByteArrayOutputStream() @@ -193,19 +236,13 @@ class XcodeTask extends DefaultTask { } /** - * Extracts all target names within the Podfile. - * - * For an app target named 'IOS-APP', likely target names are: - * IOS-APP - * IOS-APPTests - * IOS-APP WatchKit App - * IOS-APP WatchKit Extension + * Extracts xcode targets in Podfile. */ @VisibleForTesting static List extractXcodeTargets(List podfileLines) { List xcodeTargets = new ArrayList<>() for (line in podfileLines) { - Matcher matcher = (line =~ /^target '([^']*)' do$/) + Matcher matcher = (line =~ targetStartRegex) if (matcher.find()) { xcodeTargets.add(matcher.group(1)) } @@ -213,22 +250,112 @@ class XcodeTask extends DefaultTask { return xcodeTargets } + /** + * Strips certain lines from podfile. + * + * Stripping is applied from startRegex, stopping immediately before endRegex, + * the line is removed if and only if it matches stripRegex. + * Throws if startRegex is found but not endRegex. + */ + @VisibleForTesting + static List regexStripLines(List podfileLines, boolean multipleMatches, + String startRegex, String endRegex, String stripRegex) { + List result = new ArrayList<>() + boolean active = false + boolean completedFirstMatch = false + + for (line in podfileLines) { + if (completedFirstMatch && !multipleMatches) { + // Ignoring 2nd and later matches + result.add(line) + } else { + if ((line =~ startRegex).find()) { + active = true + } + if ((line =~ endRegex).find()) { + active = false + completedFirstMatch = true + } + // strip line only within 'active' range + if (!active || !(line =~ stripRegex).find()) { + result.add(line) + } + } + } + if (active) { + throw new InvalidUserDataException( + "Failed to find endRegex: ${Utils.escapeSlashyString(endRegex)}\n" + + podfileLines.join('\n')) + } + return result + } + + /** + * Insert new lines in to podfile between startRegex to endRegex. + * + * Throws error for no match or multiple matches. + */ + @VisibleForTesting + static List regexInsertLines(List podfileLines, boolean insertAfterStart, + String startRegex, String endRegex, List insertLines) { + List result = new ArrayList<>() + boolean active = false + boolean done = false + + for (line in podfileLines) { + if (done) { + result.add(line) + } else { + boolean startFoundThisLoop = false + (line =~ startRegex).find() { + active = true + startFoundThisLoop = true + assert !done + } + (line =~ endRegex).find() { + if (active) { + if (!insertAfterStart) { + result.addAll(insertLines) + } + active = false + done = true + } + } + result.add(line) + + if (startFoundThisLoop && insertAfterStart) { + result.addAll(insertLines) + } + } + } + + if (active) { + throw new InvalidUserDataException( + "Failed to find endRegex: ${Utils.escapeSlashyString(endRegex)}\n" + + podfileLines.join('\n')) + } + if (!done) { + throw new InvalidUserDataException( + "Failed to find startRegex: ${Utils.escapeSlashyString(startRegex)}\n" + + podfileLines.join('\n')) + } + return result + } + /** * Modify in place the existing podfile. */ @VisibleForTesting static void writeUpdatedPodfileIfNeeded( List podspecDetailsList, - List xcodeTargets, File podfile, Logger logger) { + XcodeTargetDetails xcodeTargetDetails, + File podfile, Logger logger) { List oldPodfileLines = podfile.readLines() List newPodfileLines = new ArrayList(oldPodfileLines) - podspecDetailsList.each { PodspecDetails podspecDetails -> - newPodfileLines = updatePodfile( - newPodfileLines, podspecDetails, - xcodeTargets, podfile, logger) - } + newPodfileLines = updatePodfile( + newPodfileLines, podspecDetailsList, xcodeTargetDetails, podfile, logger) // Write file only if it's changed if (!oldPodfileLines.equals(newPodfileLines)) { @@ -238,57 +365,78 @@ class XcodeTask extends DefaultTask { @VisibleForTesting static List updatePodfile( - List oldPodfileLines, PodspecDetails podspecDetails, - List xcodeTargets, File podfile, Logger logger) { + List podfileLines, + List podspecDetailsList, + XcodeTargetDetails xcodeTargetDetails, + File podfile, Logger logger) { + + List podfileTargets = extractXcodeTargets(podfileLines) + verifyTargets(xcodeTargetDetails.xcodeTargetsIos, podfileTargets, 'xcodeTargetsIos') + verifyTargets(xcodeTargetDetails.xcodeTargetsOsx, podfileTargets, 'xcodeTargetsOsx') + verifyTargets(xcodeTargetDetails.xcodeTargetsWatchos, podfileTargets, 'xcodeTargetsWatchos') + + if (xcodeTargetDetails.xcodeTargetsIos.isEmpty() && + xcodeTargetDetails.xcodeTargetsOsx.isEmpty() && + xcodeTargetDetails.xcodeTargetsWatchos.isEmpty()) { + // Need to warn about configuring + throw new InvalidUserDataException( + "You must configure the xcode targets for the J2ObjC Gradle Plugin.\n" + + "It must be a subset of the valid targets: '${podfileTargets.join("', '")}'\n" + + "\n" + + "j2objcConfig {\n" + + " xcodeTargetsIos 'IOS-APP', 'IOS-APPTests' // example\n" + + "}\n" + + "\n" + + "Can be optionally configured for xcodeTargetsOsx and xcodeTargetsWatchos\n") + } - List podfileTargets = extractXcodeTargets(oldPodfileLines) - List newPodfileLines = new ArrayList(oldPodfileLines) + // update pod methods + List newPodfileLines = updatePodMethods(podfileLines, podspecDetailsList, podfile) - // No targets set, then default to all - if (xcodeTargets.size() == 0) { - xcodeTargets = podfileTargets - if (logger) { - logger.debug("xcodeTargets default is all targets: '${podfileTargets.join(', ')}'") - } - } else { - for (xcodeTarget in xcodeTargets) { - if (! (xcodeTarget in podfileTargets)) { - // Error checking for zero or non-existent xcodeTargets - throw new InvalidUserDataException( - "Current xcodeTargets: xcodeTargets\n" + - "Valid xcodeTargets must be subset (likely all) of: $podfileTargets\n" + - "\n" + - "j2objcConfig {\n" + - " xcodeTargets '${podfileTargets.join(', ')}'\n" + - "}\n") - } + // Iterate over all podfileTargets as some may need to be cleared + newPodfileLines = updatePodfileTargets( + newPodfileLines, podspecDetailsList, xcodeTargetDetails) + + return newPodfileLines + } + + private static verifyTargets(List xcodeTargets, List podfileTargets, xcodeTargetsName) { + xcodeTargets.each { String xcodeTarget -> + if (! podfileTargets.contains(xcodeTarget)) { + throw new InvalidUserDataException( + "Invalid j2objcConfig { $xcodeTargetsName '$xcodeTarget' }\n" + + "Must be one of the valid targets: '${podfileTargets.join("', '")}'") } } + } + + @VisibleForTesting + static List updatePodMethods( + List podfileLines, List podspecDetailsList, File podfile) { - // Install shared shared pod for multiple targets - // http://natashatherobot.com/cocoapods-installing-same-pod-multiple-targets/ - newPodfileLines = updatePodfileMethod( - newPodfileLines, podspecDetails, podfile) + // strip all old methods + // Note: use of preserveEndLine=true so that the targetStartRegex isn't removed + List newPodfileLines = + regexStripLines(podfileLines, false, podMethodStartRegex, targetStartRegex, /.*/) - // Iterate over all podfileTargets as some may need to be cleared - for (podfileTarget in podfileTargets) { - boolean addPodMethod = podfileTarget in xcodeTargets - newPodfileLines = updatePodfileTarget( - newPodfileLines, podfileTarget, - podspecDetails.getPodMethodName(), addPodMethod) + // create new methods + List insertLines = new ArrayList<>() + insertLines.add('# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target') + podspecDetailsList.each { PodspecDetails podspecDetails -> + insertLines.addAll(podMethodLines(podspecDetails, podfile)) + insertLines.add('') } + + // insert new methods immediately before first target + newPodfileLines = regexInsertLines(newPodfileLines, false, /.*/, targetStartRegex, insertLines) + return newPodfileLines } - private static List updatePodfileMethod( - List oldPodfileLines, PodspecDetails podspecDetails, File podfile) { - - List newPodfileLines = new ArrayList<>() - boolean podMethodFound = false - boolean podMethodProcessed = false + @VisibleForTesting + static List podMethodLines( + PodspecDetails podspecDetails, File podfile) { - // Extract podspec details. Example given for debug podspec - // // Inputs: // podNameMethod: j2objc_PROJECT // podfile: /SRC/ios/Podfile @@ -308,51 +456,12 @@ class XcodeTask extends DefaultTask { // Search for pod within the xcodeTarget, until "end" is found for that target // Either update pod line in place or add line if pod doesn't exist - List newPodMethodLines = new ArrayList<>() - newPodMethodLines.add("def ${podspecDetails.getPodMethodName()}".toString()) - newPodMethodLines.add(" pod '$podspecDebugName', :configuration => ['Debug'], :path => '$pathDebug'".toString()) - newPodMethodLines.add(" pod '$podspecReleaseName', :configuration => ['Release'], :path => '$pathRelease'".toString()) - newPodMethodLines.add("end") - - oldPodfileLines.each { String line -> - // Copies each line to newPodfileLines, unless skipped - boolean skipWritingLine = false - - if (!podMethodProcessed) { - // Look for pod method start: def j2objc_shared - if (!podMethodFound) { - if (line.contains(newPodMethodLines.get(0))) { - podMethodFound = true - } - } - - if (podMethodFound) { - // Generate new pod method contents each time - skipWritingLine = true - - if (line.trim() == 'end') { - // End of old pod method - newPodfileLines.addAll(newPodMethodLines) - // Generate new pod method each time - podMethodProcessed = true - } - } - - if (line.trim().startsWith("target '")) { - // pod method wasn't found, so needs to be written anyway - newPodfileLines.addAll(newPodMethodLines) - // Blank line - newPodfileLines.add('') - podMethodProcessed = true - } - } - - if (!skipWritingLine) { - newPodfileLines.add(line) - } - } - - return newPodfileLines + List podMethodLines = new ArrayList<>() + podMethodLines.add("def ${podspecDetails.getPodMethodName()}".toString()) + podMethodLines.add(" pod '$podspecDebugName', :configuration => ['Debug'], :path => '$pathDebug'".toString()) + podMethodLines.add(" pod '$podspecReleaseName', :configuration => ['Release'], :path => '$pathRelease'".toString()) + podMethodLines.add("end") + return podMethodLines } /** @@ -362,71 +471,41 @@ class XcodeTask extends DefaultTask { * @return updated copy of Podfile (may be identical to input) */ @VisibleForTesting - static List updatePodfileTarget( - List oldPodfileLines, String xcodeTarget, - String podNameMethod, boolean addPodMethod) { - - // Indent for aesthetic reasons in Podfile - String podMethodLine = " $podNameMethod" - List newPodfileLines = new ArrayList<>() - boolean podMethodProcessed = false - boolean withinXcodeTarget = false - - oldPodfileLines.each { String line -> - - // Copies each line to newPodfileLines, unless skipped - boolean skipWritingLine = false - - // Find xcodeTarget within single quote marks - if (line.contains("'$xcodeTarget'")) { - withinXcodeTarget = true - - } else if (withinXcodeTarget) { + static List updatePodfileTargets( + List podfileLines, + List podspecDetailsList, + XcodeTargetDetails xcodeTargetDetails) { - // For upgrade from v0.4.3 to v0.5.0, basically for Xcode 7 - // TODO: remove code for plugin beta release as it's not necessary after upgrading - if (line.contains("pod 'j2objc")) { - // Old pod lines that should be removed. Example: - // pod 'j2objc-shared-debug', :configuration => ['Debug'], :path => '/Users/USERNAME/dev/taptap/base/build' - skipWritingLine = true - } + // Strip the following: + // 1) pod method calls + // 2) v0.4.3 and earlier inlined pod methods + // 3) 'platform :' lines for ios, osx & watchos + String stripRegex = /^\s*((j2objc_)|(pod 'j2objc)|(platform\s)).*/ - if (line.contains(podNameMethod)) { - // skip copying this line - skipWritingLine = true - if (podMethodProcessed) { - // repeated podName lines, drop them as they should not be here - } else { - if (addPodMethod) { - // update existing pod method line - // finding existing line makes for stable in place ordering - newPodfileLines.add(podMethodLine) - } - // If addPodMethod = false, then this line is dropped - podMethodProcessed = true - } - } else if (line.contains('end')) { - withinXcodeTarget = false - if (!podMethodProcessed) { - if (addPodMethod) { - // no existing pod method line, so add it - newPodfileLines.add(podMethodLine) - } - podMethodProcessed = true - } - } - } + List newPodfileLines = + regexStripLines(podfileLines, true, targetStartRegex, endRegex, stripRegex) - if (!skipWritingLine) { - newPodfileLines.add(line) - } + List insertLines = new ArrayList<>() + insertLines.add(' platform :INVALID') + podspecDetailsList.each { PodspecDetails podspecDetails -> + insertLines.add(" ${podspecDetails.getPodMethodName()}".toString()) } - if (!podMethodProcessed) { - throw new InvalidUserDataException( - "Unable to find Podfile target: $xcodeTarget") + xcodeTargetDetails.xcodeTargetsIos.each { String iosTarget -> + insertLines.set(0, " platform :ios, '${xcodeTargetDetails.minVersionIos}'".toString()) + String startTargetNamed = targetNamedRegex.replace('TARGET', iosTarget) + newPodfileLines = regexInsertLines(newPodfileLines, true, startTargetNamed, endRegex, insertLines) + } + xcodeTargetDetails.xcodeTargetsOsx.each { String osxTarget -> + insertLines.set(0, " platform :osx, '${xcodeTargetDetails.minVersionOsx}'".toString()) + String startTargetNamed = targetNamedRegex.replace('TARGET', osxTarget) + newPodfileLines = regexInsertLines(newPodfileLines, true, startTargetNamed, endRegex, insertLines) + } + xcodeTargetDetails.xcodeTargetsWatchos.each { String watchosTarget -> + insertLines.set(0, " platform :watchos, '${xcodeTargetDetails.minVersionWatchos}'".toString()) + String startTargetNamed = targetNamedRegex.replace('TARGET', watchosTarget) + newPodfileLines = regexInsertLines(newPodfileLines, true, startTargetNamed, endRegex, insertLines) } - return newPodfileLines } } diff --git a/src/test/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTaskTest.groovy b/src/test/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTaskTest.groovy index c978db2e..4a843fba 100644 --- a/src/test/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTaskTest.groovy +++ b/src/test/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTaskTest.groovy @@ -17,6 +17,7 @@ package com.github.j2objccontrib.j2objcgradle.tasks import com.github.j2objccontrib.j2objcgradle.J2objcConfig +import com.google.common.annotations.VisibleForTesting import org.gradle.api.InvalidUserDataException import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder @@ -36,6 +37,17 @@ class XcodeTaskTest { // TODO: use this within future tests private Project proj + XcodeTask.XcodeTargetDetails xcodeTargetDetailsIosAppOnly = + new XcodeTask.XcodeTargetDetails( + ['IOS-APP'], [], [], + // Append '.0' to version number to check that it's not using defaults + '6.0.0', '10.4.0', '1.0.0') + List podspecDetailsProj = + [new XcodeTask.PodspecDetails( + 'PROJ', + new File('/SRC/PROJ/BUILD/j2objc-PROJ-debug.podspec'), + new File('/SRC/PROJ/BUILD/j2objc-PROJ-release.podspec'))] + @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -56,7 +68,7 @@ class XcodeTaskTest { J2objcConfig j2objcConfig = proj.extensions.create('j2objcConfig', J2objcConfig, proj) j2objcConfig.xcodeProjectDir = '../ios' - j2objcConfig.xcodeTargets = ['IOS-APP'] + j2objcConfig.xcodeTargetsIos = ['IOS-APP'] XcodeTask j2objcXcode = (XcodeTask) proj.tasks.create(name: 'j2objcXcode', type: XcodeTask) j2objcXcode.verifyXcodeArgs() @@ -72,7 +84,7 @@ class XcodeTaskTest { J2objcConfig j2objcConfig = proj.extensions.create('j2objcConfig', J2objcConfig, proj) assert null == j2objcConfig.xcodeProjectDir - assert 0 == j2objcConfig.xcodeTargets.size() + assert 0 == j2objcConfig.xcodeTargetsIos.size() XcodeTask j2objcXcode = (XcodeTask) proj.tasks.create(name: 'j2objcXcode', type: XcodeTask) @@ -110,6 +122,8 @@ class XcodeTaskTest { createJ2objcConfig: true)) j2objcConfig.xcodeProjectDir = '../ios' + j2objcConfig.xcodeTargetsIos = ['IOS-APP', 'IOS-APPTests'] + j2objcConfig.minVersionIos = '6.0.0' // Podfile Write // This is outside of the project's temp directory but appears to work fine @@ -117,6 +131,8 @@ class XcodeTaskTest { File podfile = proj.file('../ios/Podfile') podfile.deleteOnExit() podfile.write( + "use_frameworks!\n" + + "\n" + "target 'IOS-APP' do\n" + "end\n" + "\n" + @@ -128,8 +144,8 @@ class XcodeTaskTest { mockProjectExec.demandExecAndReturn( proj.file('../ios').absolutePath, // working directory [ - "pod", - "install", + 'pod', + 'install', ], null, null, @@ -151,16 +167,21 @@ class XcodeTaskTest { String podNameRelease = "j2objc-${proj.name}-release" String path = "../${proj.getProjectDir().getName()}/build" List expectedPodfile = [ + "use_frameworks!", + "", + "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", "def $podNameMethod", " pod '$podNameDebug', :configuration => ['Debug'], :path => '$path'", " pod '$podNameRelease', :configuration => ['Release'], :path => '$path'", "end", "", "target 'IOS-APP' do", + " platform :ios, '6.0.0'", " $podNameMethod", "end", "", "target 'IOS-APPTests' do", + " platform :ios, '6.0.0'", " $podNameMethod", "end"] List readPodfileLines = podfile.readLines() @@ -177,7 +198,7 @@ class XcodeTaskTest { createJ2objcConfig: true)) j2objcConfig.xcodeProjectDir = 'ios' - j2objcConfig.xcodeTargets = ['IOS-APP'] + j2objcConfig.xcodeTargetsIos = ['IOS-APP'] // Needed for podspec proj.file(proj.buildDir).mkdir() @@ -193,9 +214,9 @@ class XcodeTaskTest { assert false, 'Expected Exception' } catch (InvalidUserDataException exception) { assert exception.toString().contains("Set xcodeProjectDir to the directory containing 'IOS-APP.xcodeproj':") - assert exception.toString().contains("Within that directory, create the Podfile with:") + assert exception.toString().contains('Within that directory, create the Podfile with:') assert exception.toString().contains("(cd ${proj.file('ios').absolutePath} && pod init)") - assert exception.toString().contains("sudo gem install cocoapods") + assert exception.toString().contains('sudo gem install cocoapods') } // Verify no calls to project.copy, project.delete or project.exec @@ -211,7 +232,7 @@ class XcodeTaskTest { applyJavaPlugin: true, createJ2objcConfig: true)) assert null == j2objcConfig.xcodeProjectDir - assert 0 == j2objcConfig.xcodeTargets.size() + assert 0 == j2objcConfig.xcodeTargetsIos.size() XcodeTask j2objcXcode = (XcodeTask) proj.tasks.create(name: 'j2objcXcode', type: XcodeTask) @@ -257,12 +278,149 @@ class XcodeTaskTest { } @Test - void testWriteUpdatedPodfileIfNeeded_Needed() { + void testRegexStripLines_noStartMatch() { + List oldLines = [ + 'st-art', + 'strip', + 'en-d'] + + List newLines = XcodeTask.regexStripLines( + oldLines, false, /start/, /end/, /^strip$/) + + assert oldLines == newLines + } + + @Test + void testRegexStripLines_remove() { + List oldLines = [ + 'strip outside', + 'start', + 'str-ip', + 'strip inside', + 'end'] + + List newLines = XcodeTask.regexStripLines( + oldLines, false, /start/, /end/, /strip/) + + List expectedNewLines = [ + 'strip outside', + 'start', + 'str-ip', + 'end'] + assert expectedNewLines == newLines + } + + @Test + void testRegexStripLines_removeInclusivePreserveEndLine() { + List oldLines = [ + 'BEFORE', + 'start', + 'end', + 'AFTER'] + + List newLines = XcodeTask.regexStripLines( + oldLines, true, /start/, /end/, /.*/) + + List expectedNewLines = [ + 'BEFORE', + 'end', + 'AFTER'] + assert expectedNewLines == newLines + } + + @Test + void testRegexInsertLines_afterStart() { + List oldLines = [ + 'start', + 'in-between', + 'end'] + + List insertLines = new ArrayList<>() + insertLines.add('line1') + insertLines.add('line2') + List newLines = XcodeTask.regexInsertLines( + oldLines, true, /start/, /end/, insertLines) + + List expectedNewLines = [ + 'start', + 'line1', + 'line2', + 'in-between', + 'end'] + assert expectedNewLines == newLines + } + + @Test + void testRegexInsertLines_notAfterStart() { + List oldLines = [ + 'start', + 'in-between', + 'end'] + + List newLines = XcodeTask.regexInsertLines( + oldLines, false, /start/, /end/, ['line1', 'line2']) + + List expectedNewLines = [ + 'start', + 'in-between', + 'line1', + 'line2', + 'end'] + assert expectedNewLines == newLines + } + + @Test + void testRegexInsertLines_ignoreMultipleMatches() { + List oldLines = [ + 'start', + 'end', + 'start', + 'end'] + + List newLines = XcodeTask.regexInsertLines( + oldLines, false, /start/, /end/, ['inserted']) + + List expectedNewLines = [ + 'start', + 'inserted', + 'end', + 'start', + 'end'] + assert expectedNewLines == newLines + } + + @Test + void testRegexInsertLines_noMatchStart() { + List oldLines = [ + 'start', + 'end'] + + expectedException.expect(InvalidUserDataException.class) + expectedException.expectMessage('Failed to find startRegex: /no-match-start/') + + XcodeTask.regexInsertLines(oldLines, false, /no-match-start/, /end/, new ArrayList<>()) + } + + @Test + void testRegexInsertLines_noMatchEnd() { + List oldLines = [ + 'start', + 'end'] + + expectedException.expect(InvalidUserDataException.class) + expectedException.expectMessage('Failed to find endRegex: /no-match-end/') + + XcodeTask.regexInsertLines(oldLines, false, /start/, /no-match-end/, new ArrayList<>()) + } + + @Test + void testWriteUpdatedPodfileIfNeeded_Needed_ThenNotNeeded() { // Write temp Podfile that's deleted on exit - File podfile = File.createTempFile("podfile","") + File podfile = File.createTempFile('Podfile', '') podfile.deleteOnExit() podfile.write( + "#user comment\n" + "target 'IOS-APP' do\n" + "end") @@ -271,260 +429,417 @@ class XcodeTaskTest { List podspecDetailsList = new ArrayList<>() podspecDetailsList.add(new XcodeTask.PodspecDetails( 'PROJ', - // It doesn't matter that these files don't exist, only their relative path to Podfile + // Only their relative path to Podfile matters, they don't need to exist new File(podspecBuildDir + '/j2objc-PROJ-debug.podspec'), new File(podspecBuildDir + '/j2objc-PROJ-release.podspec'))) XcodeTask.writeUpdatedPodfileIfNeeded( - podspecDetailsList, ['IOS-APP'], podfile, null) + podspecDetailsList, xcodeTargetDetailsIosAppOnly, podfile, null) // Verify modified Podfile List expectedLines = [ + "#user comment", + "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", "def j2objc_PROJ", " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", "end", "", "target 'IOS-APP' do", + " platform :ios, '6.0.0'", " j2objc_PROJ", "end"] List readPodfileLines = podfile.readLines() assert expectedLines == readPodfileLines + + // Verify unmodified on second call + // TODO: verify that file wasn't written a second time + XcodeTask.writeUpdatedPodfileIfNeeded( + podspecDetailsList, xcodeTargetDetailsIosAppOnly, podfile, null) + readPodfileLines = podfile.readLines() + assert expectedLines == readPodfileLines } @Test - void testWriteUpdatedPodfileIfNeeded_NotNeeded() { + void testUpdatePodfile_basic() { + List podfileLines = [ + "target 'IOS-APP' do", + "end"] - // Write temp Podfile that's deleted on exit - List writtenLines = [ - "def j2objc_PROJ", - " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", - " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", - "end", - "", - "target 'IOS-APP' do", - " j2objc_PROJ", - "end"] - File podfile = File.createTempFile("podfile","") - podfile.deleteOnExit() - podfile.write(writtenLines.join('\n')) + List newPodfileLines = XcodeTask.updatePodfile( + podfileLines, + podspecDetailsProj, + xcodeTargetDetailsIosAppOnly, + new File('/SRC/ios/Podfile'), + null) - // Update the Podfile - String podspecBuildDir = podfile.getParentFile().getParentFile().toString() + '/PROJ/BUILD' - List podspecDetailsList = new ArrayList<>() - podspecDetailsList.add(new XcodeTask.PodspecDetails( - 'PROJ', - new File(podspecBuildDir + '/j2objc-PROJ-debug.podspec'), - new File(podspecBuildDir + '/j2objc-PROJ-release.podspec'))) - XcodeTask.writeUpdatedPodfileIfNeeded( - podspecDetailsList, ['IOS-APP'], podfile, null) + List expectedPodfileLines = [ + "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", + "def j2objc_PROJ", + " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", + " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", + "end", + "", + "target 'IOS-APP' do", + " platform :ios, '6.0.0'", + " j2objc_PROJ", + "end"] + assert expectedPodfileLines == newPodfileLines - // Missing verification that the file wasn't written but verifies it's the same as before - List readPodfileLines = podfile.readLines() - assert writtenLines == readPodfileLines + // Second time around is a no-op + newPodfileLines = XcodeTask.updatePodfile( + newPodfileLines, + podspecDetailsProj, + xcodeTargetDetailsIosAppOnly, + new File('/SRC/ios/Podfile'), + null) + assert expectedPodfileLines == newPodfileLines } @Test - void testUpdatePodfile_Complex() { - // 1) Clean up pod method - // 2) Add pod method to IOS-APP target - // 3) Remove pod method from IOS-APPTest target + void testUpdatePodfile_complex() { List podfileLines = [ - "def j2objc_PROJ", - " RANDOM-CRUFT-TO-BE-DELETED", - " pod 'j2objc-PROJ-IGNORE', :configuration => ['Release'], :path => '/WRONG-DIR'", - "end", + "# user comment", "", "target 'IOS-APP' do", - "", - " pod 'IGNORE1', :path => 'IGNORE'", "end", - "", - "target 'IOS-APP WatchKit App' do", - " j2objc_PROJ", - " pod 'IGNORE2', :path => 'IGNORE'", + "target 'OSX-APP' do", + " j2objc_JUNK_TO_BE_DELETED", + "end", + "target 'WATCH-APP' do", "end"] + XcodeTask.XcodeTargetDetails xcodeTargetDetails = new XcodeTask.XcodeTargetDetails( + ['IOS-APP'], ['OSX-APP'], ['WATCH-APP'], + '6.0.0', '10.4.0', '1.0.0') List newPodfileLines = XcodeTask.updatePodfile( podfileLines, - new XcodeTask.PodspecDetails( - 'PROJ', - new File('/SRC/PROJ/BUILD/j2objc-PROJ-debug.podspec'), - new File('/SRC/PROJ/BUILD/j2objc-PROJ-release.podspec')), - ['IOS-APP'], + podspecDetailsProj, + xcodeTargetDetails, new File('/SRC/ios/Podfile'), null) - List expectedLines = [ + List expectedPodfileLines = [ + "# user comment", + "", + "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", "def j2objc_PROJ", " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", "end", "", "target 'IOS-APP' do", - "", - " pod 'IGNORE1', :path => 'IGNORE'", + " platform :ios, '6.0.0'", " j2objc_PROJ", "end", - "", - "target 'IOS-APP WatchKit App' do", - " pod 'IGNORE2', :path => 'IGNORE'", + "target 'OSX-APP' do", + " platform :osx, '10.4.0'", + " j2objc_PROJ", + "end", + "target 'WATCH-APP' do", + " platform :watchos, '1.0.0'", + " j2objc_PROJ", "end"] + assert expectedPodfileLines == newPodfileLines - assert expectedLines == newPodfileLines + // Second time around is a no-op + newPodfileLines = XcodeTask.updatePodfile( + newPodfileLines, + podspecDetailsProj, + xcodeTargetDetails, + new File('/SRC/ios/Podfile'), + null) + assert expectedPodfileLines == newPodfileLines + } + + @Test + void testUpdatePodfile_needJ2objcConfig() { + List podfileLines = [ + "target 'IOS-APP' do", + "end", + "target 'WATCH-APP' do", + "end"] + + expectedException.expect(InvalidUserDataException.class) + expectedException.expectMessage("You must configure the xcode targets for the J2ObjC Gradle Plugin") + expectedException.expectMessage("It must be a subset of the valid targets: 'IOS-APP', 'WATCH-APP'") + expectedException.expectMessage("xcodeTargetsIos 'IOS-APP', 'IOS-APPTests' // example") + + XcodeTask.updatePodfile( + podfileLines, + [], + new XcodeTask.XcodeTargetDetails( + [], [], [], + '6.0.0', '10.4.0', '1.0.0'), + null, + null) } @Test - // If xcodeTargets == [], then include all targets - void testUpdatePodfile_DefaultsToAllTargets() { + void testUpdatePodfile_invalidTarget() { List podfileLines = [ "target 'IOS-APP' do", - " pod 'IGNORE1', :path => 'IGNORE'", "end", + "target 'WATCH-APP' do", + "end"] + + expectedException.expect(InvalidUserDataException.class) + expectedException.expectMessage("Invalid j2objcConfig { xcodeTargetsIos 'TARGET-DOES-NOT-EXIST' }") + expectedException.expectMessage("Must be one of the valid targets: 'IOS-APP', 'WATCH-APP'") + + XcodeTask.updatePodfile( + podfileLines, + [], + new XcodeTask.XcodeTargetDetails( + ['TARGET-DOES-NOT-EXIST'], [], [], + '6.0.0', '10.4.0', '1.0.0'), + null, + null) + } + + @Test + void testUpdatePodMethods_basic() { + List podfileLines = [ + "# user comment", "", - "target 'IOS-APPTests' do", - " pod 'IGNORE2', :path => 'IGNORE'", + // "# J2ObjC Gradle Plugin..." line is missing to make sure regex handles both cases + "def j2objc_TO_BE_DELETED", + " pod 'j2objc-TO_BE_DELETED-debug', :configuration => ['Debug'], :path => '../shared/build'", + " pod 'j2objc-TO_BE_DELETED-release', :configuration => ['Release'], :path => '../shared/build'", "end", "", - "target 'IOS-APP WatchKit App' do", - " j2objc_PROJ", - " pod 'IGNORE3', :path => 'IGNORE'", + "target 'IOS-APP' do", "end"] - List newPodfileLines = XcodeTask.updatePodfile( + List newPodfileLines = XcodeTask.updatePodMethods( podfileLines, - new XcodeTask.PodspecDetails( - 'PROJ', - new File('/SRC/PROJ/BUILD/j2objc-PROJ-debug.podspec'), - new File('/SRC/PROJ/BUILD/j2objc-PROJ-release.podspec')), - [], // xcodeTargets is empty to test default of all targets - new File('/SRC/ios/Podfile'), - null) + podspecDetailsProj, + new File('/SRC/ios/Podfile')) List expectedLines = [ + "# user comment", + "", + "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", "def j2objc_PROJ", " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", "end", "", "target 'IOS-APP' do", - " pod 'IGNORE1', :path => 'IGNORE'", - " j2objc_PROJ", + "end"] + + assert expectedLines == newPodfileLines + } + + @Test + void testUpdatePodMethods() { + List podfileLines = [ + "# user comment", + "", + "# J2ObjC Gradle Plugin - DO NOT MODIFY from this line to the first target", + "def j2objc_TO_BE_DELETED", + " pod 'j2objc-TO_BE_DELETED-debug', :configuration => ['Debug'], :path => '../shared/build'", + " pod 'j2objc-TO_BE_DELETED-release', :configuration => ['Release'], :path => '../shared/build'", "end", "", - "target 'IOS-APPTests' do", - " pod 'IGNORE2', :path => 'IGNORE'", - " j2objc_PROJ", + "", + "target 'IOS-APP' do", + " j2objc_DELETED_BY_ANOTHER_METHOD", + "end"] + + List newPodfileLines = XcodeTask.updatePodMethods( + podfileLines, + [new XcodeTask.PodspecDetails( + 'PROJA', + new File('/SRC/PROJA/BUILD/j2objc-PROJA-debug.podspec'), + new File('/SRC/PROJA/BUILD/j2objc-PROJB-release.podspec')), + new XcodeTask.PodspecDetails( + 'PROJB', + new File('/SRC/PROJB/BUILD/j2objc-PROJB-debug.podspec'), + new File('/SRC/PROJB/BUILD/j2objc-PROJB-release.podspec'))], + new File('/SRC/ios/Podfile')) + + List expectedLines = [ + "# user comment", + "", + "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", + "def j2objc_PROJA", + " pod 'j2objc-PROJA-debug', :configuration => ['Debug'], :path => '../PROJA/BUILD'", + " pod 'j2objc-PROJB-release', :configuration => ['Release'], :path => '../PROJA/BUILD'", "end", "", - "target 'IOS-APP WatchKit App' do", - " j2objc_PROJ", - " pod 'IGNORE3', :path => 'IGNORE'", + "def j2objc_PROJB", + " pod 'j2objc-PROJB-debug', :configuration => ['Debug'], :path => '../PROJB/BUILD'", + " pod 'j2objc-PROJB-release', :configuration => ['Release'], :path => '../PROJB/BUILD'", + "end", + "", + "target 'IOS-APP' do", + " j2objc_DELETED_BY_ANOTHER_METHOD", "end"] assert expectedLines == newPodfileLines } - @Test(expected = InvalidUserDataException.class) - void testUpdatePodfileTarget_TargetNotFound() { - List podfileLines = [ - "target 'IOS-APP' do", - "pod 'j2objc-PROJ-debug', :path => '/Users/USERNAME/dev/workspace/shared/build'", + @Test + void testPodMethodLines() { + List podMethodLines = XcodeTask.podMethodLines( + podspecDetailsProj.get(0), + new File('/SRC/ios/Podfile')) + + List expectedLines = [ + "def j2objc_PROJ", + " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", + " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", "end"] - XcodeTask.updatePodfileTarget( - podfileLines, 'XCODE_TARGET_DOES_NOT_EXIST', - 'j2objc_PROJ', true) + assert expectedLines == podMethodLines } @Test - void testUpdatePodfileTarget_AddAndRemove() { - List podfileTargetEmpty = [ + void testUpdatePodfileTargets_basic() { + List podfileLines = [ "target 'IOS-APP' do", + "end", + "target 'IOS-APP-B' do", "end"] - List podfileTargetWithMethod = [ + // Add 1st target + List newPodfileLines = XcodeTask.updatePodfileTargets( + podfileLines, + podspecDetailsProj, + xcodeTargetDetailsIosAppOnly) + List expectedPodfileLines = [ "target 'IOS-APP' do", + " platform :ios, '6.0.0'", " j2objc_PROJ", + "end", + "target 'IOS-APP-B' do", "end"] + assert expectedPodfileLines == newPodfileLines + + // Repeated call is noop + newPodfileLines = XcodeTask.updatePodfileTargets( + newPodfileLines, + podspecDetailsProj, + xcodeTargetDetailsIosAppOnly) + assert expectedPodfileLines == newPodfileLines - // Remove no-op - List newPodfileLines = XcodeTask.updatePodfileTarget( - podfileTargetEmpty, 'IOS-APP', 'j2objc_PROJ', false) - assert newPodfileLines == newPodfileLines - - // Add - newPodfileLines = XcodeTask.updatePodfileTarget( - podfileTargetEmpty, 'IOS-APP', 'j2objc_PROJ', true) - assert podfileTargetWithMethod == newPodfileLines - - // Add no-op - newPodfileLines = XcodeTask.updatePodfileTarget( - podfileTargetEmpty, 'IOS-APP', 'j2objc_PROJ', true) - assert podfileTargetWithMethod == newPodfileLines - - // Remove - newPodfileLines = XcodeTask.updatePodfileTarget( - podfileTargetEmpty, 'IOS-APP', 'j2objc_PROJ', false) - assert newPodfileLines == newPodfileLines + // Swap to 2nd target + newPodfileLines = XcodeTask.updatePodfileTargets( + newPodfileLines, + podspecDetailsProj, + new XcodeTask.XcodeTargetDetails( + ['IOS-APP-B'], [], [], + '6.0.0', '10.4.0', '1.0.0')) + List expectedPodfileLinesAfterSwap = [ + "target 'IOS-APP' do", + "end", + "target 'IOS-APP-B' do", + " platform :ios, '6.0.0'", + " j2objc_PROJ", + "end"] + assert expectedPodfileLinesAfterSwap == newPodfileLines } @Test - void testUpdatePodfileTarget_PreserveOrdering() { + void testUpdatePodfileTargets_allPlatformsMultipleProjectsAndTests() { List podfileLines = [ - "target 'TARGET_A' do", - " pod 'IGNORE1', :path => 'IGNORE'", - " j2objc_PROJ", + // pod method should not be affected by removal of the old code + "target 'IOS-APP' do", "end", - "", - "target 'TARGET_B' do", - " j2objc_PROJ", - " pod 'IGNORE2', :path => 'IGNORE'", + "target 'IOS-APPTests' do", + "end", + "target 'OSX-APP' do", + "end", + "target 'OSX-APPTests' do", + "end", + "target 'WATCH-APP' do", + "end", + "target 'WATCH-APPTests' do", "end"] - List newPodfileLines = XcodeTask.updatePodfileTarget( - podfileLines, 'TARGET_A', 'j2objc_PROJ', true) - newPodfileLines = XcodeTask.updatePodfileTarget( - newPodfileLines, 'TARGET_B', 'j2objc_PROJ', true) + // Update podfile targets + List newPodfileLines = XcodeTask.updatePodfileTargets( + podfileLines, + [new XcodeTask.PodspecDetails('PROJ_A', null, null), + new XcodeTask.PodspecDetails('PROJ_B', null, null)], + new XcodeTask.XcodeTargetDetails( + ['IOS-APP', 'IOS-APPTests'], ['OSX-APP', 'OSX-APPTests'], ['WATCH-APP', 'WATCH-APPTests'], + '6.0.0', '10.4.0', '1.0.0')) - // Preserves the ordering of the lines - assert podfileLines == newPodfileLines + List expectedPodfileLines = [ + "target 'IOS-APP' do", + " platform :ios, '6.0.0'", + " j2objc_PROJ_A", + " j2objc_PROJ_B", + "end", + "target 'IOS-APPTests' do", + " platform :ios, '6.0.0'", + " j2objc_PROJ_A", + " j2objc_PROJ_B", + "end", + "target 'OSX-APP' do", + " platform :osx, '10.4.0'", + " j2objc_PROJ_A", + " j2objc_PROJ_B", + "end", + "target 'OSX-APPTests' do", + " platform :osx, '10.4.0'", + " j2objc_PROJ_A", + " j2objc_PROJ_B", + "end", + "target 'WATCH-APP' do", + " platform :watchos, '1.0.0'", + " j2objc_PROJ_A", + " j2objc_PROJ_B", + "end", + "target 'WATCH-APPTests' do", + " platform :watchos, '1.0.0'", + " j2objc_PROJ_A", + " j2objc_PROJ_B", + "end"] + assert expectedPodfileLines == newPodfileLines } @Test - // For upgrade from v0.4.3 to v0.5.0 - void testUpdatePodfileTarget_PodMethodUpgrade() { + void testUpdatePodfileTargets_ignoreExistingLines() { List podfileLines = [ - // pod method should not be affected by removal of the old code - "def j2objc_PROJ", - " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '/Users/USERNAME/dev/workspace/shared/build'", - " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '/Users/USERNAME/dev/workspace/shared/build'", - "end", - "", - "target 'TARGET' do", + "target 'IOS-APP' do", + // Existing lines should be ignored + " pod 'IGNORE1', :path => 'IGNORE1'", + " pod 'IGNORE2', :path => 'IGNORE2'", + // v0.4.3 upgrade to discard old inlining of pod method " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '/Users/USERNAME/dev/workspace/shared/build'", " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '/Users/USERNAME/dev/workspace/shared/build'", - " pod 'IGNORE2', :path => 'IGNORE'", "end"] + // Update podfile targets + List newPodfileLines = XcodeTask.updatePodfileTargets( + podfileLines, + podspecDetailsProj, + xcodeTargetDetailsIosAppOnly) + List expectedPodfileLines = [ - "def j2objc_PROJ", - " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '/Users/USERNAME/dev/workspace/shared/build'", - " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '/Users/USERNAME/dev/workspace/shared/build'", - "end", - "", - "target 'TARGET' do", - " pod 'IGNORE2', :path => 'IGNORE'", + "target 'IOS-APP' do", + " platform :ios, '6.0.0'", " j2objc_PROJ", + " pod 'IGNORE1', :path => 'IGNORE1'", + " pod 'IGNORE2', :path => 'IGNORE2'", "end"] - - // First update cleans up the Podfile, replacing within targets definitions with pod method - List newPodfileLines = XcodeTask.updatePodfileTarget( - podfileLines, 'TARGET', 'j2objc_PROJ', true) assert expectedPodfileLines == newPodfileLines + } - // Second update has no effect - newPodfileLines = XcodeTask.updatePodfileTarget( - newPodfileLines, 'TARGET', 'j2objc_PROJ', true) - assert expectedPodfileLines == newPodfileLines + // Better error is given in parent call + @Test(expected = InvalidUserDataException.class) + void testUpdatePodfileTargets_TargetNotFound() { + List podfileLines = [ + "target 'IOS-APP' do", + "end"] + + XcodeTask.updatePodfileTargets( + podfileLines, + podspecDetailsProj, + new XcodeTask.XcodeTargetDetails( + ['TARGET-DOES-NOT-EXIST'], [], [], + '6.0.0', '10.4.0', '1.0.0')) } }