diff --git a/FAQ.md b/FAQ.md index 53358577..b5f49492 100644 --- a/FAQ.md +++ b/FAQ.md @@ -32,6 +32,7 @@ Paste the results below, replacing existing contents. - [Why is my clean build failing?](#why-is-my-clean-build-failing) - [How do I include Java files from additional source directories?](#how-do-i-include-java-files-from-additional-source-directories) - [How do I develop with Swift?](#how-do-i-develop-with-swift) +- [How do I manually configure the Cocoapods Podfile?](#how-do-i-manually-configure-the-cocoapods-podfile) - [How do I manually configure my Xcode project to use the translated libraries?](#how-do-i-manually-configure-my-xcode-project-to-use-the-translated-libraries) - [How do I update my J2ObjC translated code from Xcode?](#how-do-i-update-my-j2objc-translated-code-from-xcode) - [How do I work with Package Prefixes?](#how-do-i-work-with-package-prefixes) @@ -254,6 +255,7 @@ with `translateArgs`. Make sure your arguments are separate strings, not a single space-concatenated string. ```gradle +// File: shared/build.gradle j2objcConfig { // CORRECT translateArgs '-use-arc' @@ -302,6 +304,7 @@ For example, if you want to include files from `src-gen/base` both into your JAR your Objective C libraries, then add to your `shared/build.gradle`: ```gradle +// File: shared/build.gradle sourceSets { main { java { @@ -333,6 +336,57 @@ you'd like to access from Swift code. ``` +### How do I manually configure the Cocoapods Podfile? + +The plugin will try to automatically update the Cocoapods Podfile but that may fail if +the Podfile is too complex. In that situation, you can manually configure the pod method. + +```gradle +// File: shared/build.gradle +j2objcConfig { + xcodeTargetsManualConfig true + ... +} +``` + +The "pod method" definition will still be added automatically (e.g. +`def j2objc_shared...`). However, the "pod method" will not be added to any +targets, so that needs to be done manually. See example of the Podfile below: + +``` +// File: ios/Podfile +... + +# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block +def j2objc_shared + pod 'j2objc-shared-debug', :configuration => ['Debug'], :path => '../shared/build/j2objcOutputs' + pod 'j2objc-shared-release', :configuration => ['Release'], :path => '../shared/build/j2objcOutputs' +end +# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END + + + ... + # NOTE: this line must be added manually for the relevant targets: + j2objc_shared + ... +end +``` + +To disable all modifications of the Podfile, disable the `j2objcXcode` task. +This will also skip the `pod install` step. The podspec files will still +be written (done by the `j2objcPodspec` task). + +```gradle +// File: shared/build.gradle +j2objcConfig { + ... +} +j2objcXcode { + enabled = false +} +``` + + ### How do I manually configure my Xcode project to use the translated libraries? Using CocoaPods is the quickest way to use the plugin. To configure Xcode manually, diff --git a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy index 6c0bef1c..32a99c38 100644 --- a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/J2objcConfig.groovy @@ -712,6 +712,17 @@ class J2objcConfig { appendArgs(this.xcodeTargetsWatchos, 'xcodeTargetsWatchos', false, xcodeTargetsWatchos) } + /** + * Allows manual config of Xcode targets in the Podfile (default is false). + * + * When set to true, this allows manual configuring of the Podfile targets. + * This is necessary when your Podfile is too complex to be automatically + * updated. It will still add the "Pod Method" (e.g. j2objc_shared) but it + * will not update the targets within the Podfile. When used, you must also + * set xcodeTargets{Ios|Osx|Watchos) to empty. + */ + boolean xcodeTargetsManualConfig = false + protected boolean finalConfigured = false /** 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 3d99c750..be04f7a0 100644 --- a/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTask.groovy +++ b/src/main/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTask.groovy @@ -47,12 +47,20 @@ 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*/ + public static final String targetEndRegex = /^\s*end\s*/ + + public static final String podMethodsStart = "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block" + public static final String podMethodsEnd = "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END" + public static final String podMethodStartRegexOLD = /^\s*((def\s*j2objc_)|(# J2ObjC Gradle Plugin)).*/ + + public static final String neverMatchesRegex = /a^/ // http://stackoverflow.com/a/940840/1509221 @Input @Optional String getXcodeProjectDir() { return J2objcConfig.from(project).xcodeProjectDir } + @Input + boolean getXcodeTargetsManualConfig() { return J2objcConfig.from(project).xcodeTargetsManualConfig } + boolean isTaskActive() { return getXcodeProjectDir() != null } @Input @@ -200,7 +208,7 @@ class XcodeTask extends DefaultTask { getXcodeTargetsIos(), getXcodeTargetsOsx(), getXcodeTargetsWatchos(), getMinVersionIos(), getMinVersionOsx(), getMinVersionWatchos()) - writeUpdatedPodfileIfNeeded(podspecDetailsList, xcodeTargetDetails, podfile) + writeUpdatedPodfileIfNeeded(podspecDetailsList, xcodeTargetDetails, xcodeTargetsManualConfig, podfile) // install the pod ByteArrayOutputStream stdout = new ByteArrayOutputStream() @@ -279,51 +287,102 @@ class XcodeTask extends DefaultTask { /** * Strips certain lines from podfile. * - * Stripping is applied from startRegex, stopping immediately before endRegex, + * Stripping is applied from startRegex, stopping immediately before targetEndRegex, * the line is removed if and only if it matches stripRegex. - * Throws if startRegex is found but not endRegex. + * Throws if startRegex is found but not targetEndRegex. */ @VisibleForTesting - static List regexStripLines(List podfileLines, boolean multipleMatches, - String startRegex, String endRegex, String stripRegex) { - List result = new ArrayList<>() - boolean active = false - boolean completedFirstMatch = false + static List regexStripLines( + List podfileLines, + String startRegex, String endRegex, String stripRegex) { + + return regexModifyLines( + podfileLines, + false, // insertAfterStart + false, // matchExactlyOnce + true, // preserveEndLine + startRegex, endRegex, stripRegex, null) + } - 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 + /** + * Replaces lines between two regexps, including start and end lines + */ + @VisibleForTesting + static List regexReplaceLines( + List podfileLines, boolean preserveEndLine, + String startRegex, String endRegex, List newPodFileLines) { + + return regexModifyLines( + podfileLines, + true, // insertAfterStart + true, // matchExactlyOnce + preserveEndLine, + startRegex, + endRegex, + /.*/, // strip everything + newPodFileLines) + } + + /** + * Insert new lines in to podfile between startRegex to targetEndRegex. + * + * Throws error if no match for startRegex or targetEndRegex. + */ + @VisibleForTesting + static List regexInsertLines( + List podfileLines, boolean insertAfterStart, + String startRegex, String endRegex, List insertLines) { + + return regexModifyLines( + podfileLines, + insertAfterStart, + true, // matchExactlyOnce + false, // preserveEndLine + startRegex, + endRegex, + neverMatchesRegex, // no striping of anything, so no replacement + insertLines) + } + + /** + * Insert new lines in to podfile before line + * + * Throws error if no match for regex. + */ + @VisibleForTesting + static List regexInsertLinesBefore( + List podfileLines, String insertBeforeRegex, List insertLines) { + + return regexModifyLines( + podfileLines, + false, // insertAfterStart - insert before targetEndRegex + true, // matchExactlyOnce + true, // preserveEndLine + /.*/, // startRegex + insertBeforeRegex, // targetEndRegex + neverMatchesRegex, // no striping of anything, so no replacement + insertLines) } /** - * Insert new lines in to podfile between startRegex to endRegex. + * Modify Podfile lines between startRegex to targetEndRegex. + * @param podfileLines to be modified + * @param insertAfterStart true to add after startRegex, false to add before targetEndRegex + * @param matchExactlyOnce throw error if no startRegex / targetEndRegex match found + * @param preserveEndLine so retain even if when it matches stripRegex + * @param startRegex to match line against + * @param endRegex to match line against + * @param insertLines to be added, can be null * - * Throws error for no match or multiple matches. + * @throws InvalidUserDataException for unpaired startRegex / targetEndRegex or no matches + * when matchExactlyOnce is true */ @VisibleForTesting - static List regexInsertLines(List podfileLines, boolean insertAfterStart, - String startRegex, String endRegex, List insertLines) { + static List regexModifyLines( + List podfileLines, + boolean insertAfterStart, boolean matchExactlyOnce, boolean preserveEndLine, + String startRegex, String endRegex, String stripRegex, List insertLines) { + List result = new ArrayList<>() boolean active = false boolean done = false @@ -332,42 +391,122 @@ class XcodeTask extends DefaultTask { 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) { + boolean startMatch = false + boolean endMatch = false + + if (!active) { + (line =~ startRegex).find() { + active = true + startMatch = true + assert !done + } + } else { + // active == true + (line =~ endRegex).find() { + endMatch = true + // Insert lines before end + if (!insertAfterStart && insertLines != null) { result.addAll(insertLines) } - active = false - done = true } } - result.add(line) - if (startFoundThisLoop && insertAfterStart) { + // Drop lines in active section when matches stripRegex + if (!active || !(line =~ stripRegex).find() || (preserveEndLine && endMatch)) { + result.add(line) + } + + // Insert lines after start + if (insertAfterStart && startMatch && insertLines != null) { result.addAll(insertLines) } + + if (endMatch) { + // Now inactive again + active = false + if (matchExactlyOnce) { + done = true + } + } } } if (active) { throw new InvalidUserDataException( - "Failed to find endRegex: ${Utils.escapeSlashyString(endRegex)}\n" + - podfileLines.join('\n')) + "Failed to find targetEndRegex: ${Utils.escapeSlashyString(endRegex)}\n" + + podfileLines.join('\n') + "\n" + + "\n" + + "For a complex podfile, it may need manual configuration:\n" + + "https://github.com/j2objc-contrib/j2objc-gradle/blob/master/FAQ.md#how-do-i-manually-configure-the-cocoapods-podfile") } - if (!done) { + if (matchExactlyOnce && !done) { throw new InvalidUserDataException( "Failed to find startRegex: ${Utils.escapeSlashyString(startRegex)}\n" + - podfileLines.join('\n')) + podfileLines.join('\n') + "\n" + + "\n" + + "For a complex podfile, it may need manual configuration:\n" + + "https://github.com/j2objc-contrib/j2objc-gradle/blob/master/FAQ.md#how-do-i-manually-configure-the-cocoapods-podfile") } return result } + /** + * Checks if Podfile line matches regex + */ + @VisibleForTesting + static boolean regexMatchesLine(List podfileLines, String regex) { + return podfileLines.any { String line -> + return (line =~ regex) + } + } + + /** + * Adds new pod method when podfile has never had pod method before + * + * Most likely added before "target 'IOS-APP' do". If no 'correct' + * place is found, it will be appended at the end of the podfile. + */ + @VisibleForTesting + static List addNewPodMethod(List podfileLines, List insertLines) { + + println "addNewPodMethod: $insertLines" + + List preambleLines = [ + 'source ', + 'platform ', + 'xcodeproj ', + 'workspace ', + 'inhibit_all_warnings!', + 'use_frameworks!', + 'pod ', + '#'] // comment line + + // Default is to add at the end of the Podfile + int insertIndex = podfileLines.size() + + int lineIdx = 0 + for (podfileLine in podfileLines) { + boolean matchesPreambleLine = false + String trimmed = podfileLine.trim() + for (String preambleLine in preambleLines) { + if (trimmed.startsWith(preambleLine)) { + matchesPreambleLine = true + break + } + } + + if (!matchesPreambleLine && !trimmed.isEmpty()) { + insertIndex = lineIdx + break + } + lineIdx++ + } + + // Prompts insert after end of the array + podfileLines.addAll(insertIndex, insertLines) + return podfileLines + } + /** * Modify in place the existing podfile. */ @@ -375,13 +514,14 @@ class XcodeTask extends DefaultTask { static void writeUpdatedPodfileIfNeeded( List podspecDetailsList, XcodeTargetDetails xcodeTargetDetails, + boolean xcodeTargetsManualConfig, File podfile) { List oldPodfileLines = podfile.readLines() List newPodfileLines = new ArrayList(oldPodfileLines) newPodfileLines = updatePodfile( - newPodfileLines, podspecDetailsList, xcodeTargetDetails, podfile) + newPodfileLines, podspecDetailsList, xcodeTargetDetails, xcodeTargetsManualConfig, podfile) // Write file only if it's changed if (!oldPodfileLines.equals(newPodfileLines)) { @@ -394,33 +534,61 @@ class XcodeTask extends DefaultTask { List podfileLines, List podspecDetailsList, XcodeTargetDetails xcodeTargetDetails, + boolean xcodeTargetsManualConfig, File podfile) { - List podfileTargets = extractXcodeTargets(podfileLines) - verifyTargets(xcodeTargetDetails.xcodeTargetsIos, podfileTargets, 'xcodeTargetsIos') - verifyTargets(xcodeTargetDetails.xcodeTargetsOsx, podfileTargets, 'xcodeTargetsOsx') - verifyTargets(xcodeTargetDetails.xcodeTargetsWatchos, podfileTargets, 'xcodeTargetsWatchos') + List newPodfileLines - if (xcodeTargetDetails.xcodeTargetsIos.isEmpty() && - xcodeTargetDetails.xcodeTargetsOsx.isEmpty() && - xcodeTargetDetails.xcodeTargetsWatchos.isEmpty()) { - // Give example for configuring iOS as that's the common case - 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") + boolean xcodeTargetsAllEmpty = + xcodeTargetDetails.xcodeTargetsIos.isEmpty() && + xcodeTargetDetails.xcodeTargetsOsx.isEmpty() && + xcodeTargetDetails.xcodeTargetsWatchos.isEmpty() + + if (xcodeTargetsManualConfig) { + if (!xcodeTargetsAllEmpty) { + throw new InvalidUserDataException( + "Xcode targets and versions must all be blank when using xcodeTargetsManualConfig.\n" + + "Please update j2objcConfig in your gradle file by removing:\n" + + "1) xcodeTargetsIos, xcodeTargetsOsx & xcodeTargetsWatchos\n" + + "2) minVersionIos, minVersionOsx & minVersionWatchos") + } + newPodfileLines = podfileLines + } else { + // xcodeTargetsManualConfig = false (default) + List podfileTargets = extractXcodeTargets(podfileLines) + verifyTargets(xcodeTargetDetails.xcodeTargetsIos, podfileTargets, 'xcodeTargetsIos') + verifyTargets(xcodeTargetDetails.xcodeTargetsOsx, podfileTargets, 'xcodeTargetsOsx') + verifyTargets(xcodeTargetDetails.xcodeTargetsWatchos, podfileTargets, 'xcodeTargetsWatchos') + + if (xcodeTargetsAllEmpty) { + if (podfileTargets.isEmpty()) { + // No targets found indicates complex podfile + throw new InvalidUserDataException( + "You must configure the xcode targets for the J2ObjC Gradle Plugin.\n" + + "The Plugin was unable to find the targets (regex matching ${Utils.escapeSlashyString(targetStartRegex)}),\n" + + "so you will need to manually configure this. See instructions:\n" + + "https://github.com/j2objc-contrib/j2objc-gradle/blob/master/FAQ.md#how-do-i-manually-configure-the-cocoapods-podfile") + } else { + // Common case + 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" + + "\n" + + "NOTE: if your Podfile is too complex, you may need manual configuration:\n" + + "https://github.com/j2objc-contrib/j2objc-gradle/blob/master/FAQ.md#how-do-i-manually-configure-the-cocoapods-podfile") + } + } + newPodfileLines = updatePodfileTargets(podfileLines, podspecDetailsList, xcodeTargetDetails) } // update pod methods - List newPodfileLines = updatePodMethods(podfileLines, podspecDetailsList, podfile) - - // update pod targets - newPodfileLines = updatePodfileTargets(newPodfileLines, podspecDetailsList, xcodeTargetDetails) + newPodfileLines = updatePodMethods(newPodfileLines, podspecDetailsList, podfile) return newPodfileLines } @@ -428,9 +596,18 @@ class XcodeTask extends DefaultTask { 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("', '")}'") + if (podfileTargets.isEmpty()) { + throw new InvalidUserDataException( + "Invalid j2objcConfig { $xcodeTargetsName '$xcodeTarget' }\n" + + "The J2ObjC Gradle Plugin can't determine the possible targets,\n" + + "so this may need manual configuration:\n" + + "https://github.com/j2objc-contrib/j2objc-gradle/blob/master/FAQ.md#how-do-i-manually-configure-the-cocoapods-podfile") + } else { + // Should be more common error + throw new InvalidUserDataException( + "Invalid j2objcConfig { $xcodeTargetsName '$xcodeTarget' }\n" + + "Must be one of the valid targets: '${podfileTargets.join("', '")}'") + } } } } @@ -439,23 +616,29 @@ class XcodeTask extends DefaultTask { static List updatePodMethods( List podfileLines, List podspecDetailsList, File podfile) { - // strip all old methods - // Note: use of preserveEndLine=true so that the targetStartRegex isn't removed - List newPodfileLines = - regexStripLines(podfileLines, false, podMethodStartRegex, targetStartRegex, /.*/) - // create new methods List insertLines = new ArrayList<>() - insertLines.add('# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target') + insertLines.add(podMethodsStart) podspecDetailsList.each { PodspecDetails podspecDetails -> insertLines.addAll(podMethodLines(podspecDetails, podfile)) - insertLines.add('') } + insertLines.add(podMethodsEnd) - // insert new methods immediately before first target - newPodfileLines = regexInsertLines(newPodfileLines, false, /.*/, targetStartRegex, insertLines) + // New Format => update in place + if (regexMatchesLine(podfileLines, podMethodsStart)) { + return regexReplaceLines(podfileLines, false, podMethodsStart, podMethodsEnd, insertLines) + } - return newPodfileLines + // Old format => update to new format (occurs for v0.5.0 => v0.5.1) + if (regexMatchesLine(podfileLines, podMethodStartRegexOLD)) { + insertLines.add('') + return regexReplaceLines(podfileLines, true, podMethodStartRegexOLD, targetStartRegex, insertLines) + } + + // No existing podMethod, add blank lines to wrap then insert + insertLines.add(0, '') + insertLines.add('') + return addNewPodMethod(podfileLines, insertLines) } @VisibleForTesting @@ -508,7 +691,7 @@ class XcodeTask extends DefaultTask { String stripRegex = /^\s*((j2objc_)|(pod 'j2objc)|(platform\s)).*/ List newPodfileLines = - regexStripLines(podfileLines, true, targetStartRegex, endRegex, stripRegex) + regexStripLines(podfileLines, targetStartRegex, targetEndRegex, stripRegex) List insertLines = new ArrayList<>() insertLines.add(' platform :INVALID') @@ -519,17 +702,17 @@ class XcodeTask extends DefaultTask { 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) + newPodfileLines = regexInsertLines(newPodfileLines, true, startTargetNamed, targetEndRegex, 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) + newPodfileLines = regexInsertLines(newPodfileLines, true, startTargetNamed, targetEndRegex, 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) + newPodfileLines = regexInsertLines(newPodfileLines, true, startTargetNamed, targetEndRegex, 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 c1e2e5d9..36580ced 100644 --- a/src/test/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTaskTest.groovy +++ b/src/test/groovy/com/github/j2objccontrib/j2objcgradle/tasks/XcodeTaskTest.groovy @@ -43,9 +43,12 @@ class XcodeTaskTest { '6.0.0', '10.6.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'))] + 'PROJ', + new File('/SRC/PROJ/BUILD/j2objc-PROJ-debug.podspec'), + new File('/SRC/PROJ/BUILD/j2objc-PROJ-release.podspec'))] + XcodeTask.XcodeTargetDetails xcodeTargetDetailsEmpty = + new XcodeTask.XcodeTargetDetails( + [], [], [], null, null, null) @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -184,11 +187,13 @@ class XcodeTaskTest { List expectedPodfile = [ "use_frameworks!", "", - "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", "def $podNameMethod", " pod '$podNameDebug', :configuration => ['Debug'], :path => '$path'", " pod '$podNameRelease', :configuration => ['Release'], :path => '$path'", "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", "", "target 'IOS-APP' do", " platform :ios, '6.1.0'", @@ -282,11 +287,25 @@ class XcodeTaskTest { 'en-d'] List newLines = XcodeTask.regexStripLines( - oldLines, false, /start/, /end/, /^strip$/) + oldLines, /start/, /end/, /^strip$/) assert oldLines == newLines } + @Test + void testRegexStripLines_noEndMatchException() { + List oldLines = [ + 'start', + 'strip', + 'en-d'] + + expectedException.expect(InvalidUserDataException.class) + expectedException.expectMessage('Failed to find targetEndRegex: /end/') + + List newLines = XcodeTask.regexStripLines( + oldLines, /start/, /end/, /^strip$/) + } + @Test void testRegexStripLines_remove() { List oldLines = [ @@ -297,7 +316,7 @@ class XcodeTaskTest { 'end'] List newLines = XcodeTask.regexStripLines( - oldLines, false, /start/, /end/, /strip/) + oldLines, /start/, /end/, /strip/) List expectedNewLines = [ 'strip outside', @@ -316,7 +335,7 @@ class XcodeTaskTest { 'AFTER'] List newLines = XcodeTask.regexStripLines( - oldLines, true, /start/, /end/, /.*/) + oldLines, /start/, /end/, /.*/) List expectedNewLines = [ 'BEFORE', @@ -325,6 +344,112 @@ class XcodeTaskTest { assert expectedNewLines == newLines } + @Test + void testRegexStripLines_multipleMatches() { + List oldLines = [ + 'start', + 'strip', + 'end', + '', + 'start', + 'strip', + 'end'] + + List newLines = XcodeTask.regexStripLines( + oldLines, /start/, /end/, /strip/) + + List expectedNewLines = [ + 'start', + 'end', + '', + 'start', + 'end'] + assert expectedNewLines == newLines + } + + @Test + void testRegexReplaceLines_noChange() { + List oldLines = [ + 'strip outside', + 'start', + 'replace', + 'replace too', + 'end'] + + List newLines = XcodeTask.regexReplaceLines( + oldLines, false, /start/, /end/, ['start', 'replace', 'replace too', 'end']) + + List expectedNewLines = [ + 'strip outside', + 'start', + 'replace', + 'replace too', + 'end'] + + assert expectedNewLines == newLines + } + + @Test + void testRegexReplaceLInes_replace() { + List oldLines = [ + 'strip outside', + 'start', + 'replace1', + 'replace2', + 'end'] + + List newLines = XcodeTask.regexReplaceLines( + oldLines, false, /start/, /end/, ['replacement1', 'replacement2']) + + List expectedNewLines = [ + 'strip outside', + 'replacement1', + 'replacement2'] + + assert expectedNewLines == newLines + } + + @Test + void testRegexReplaceLInes_preserveEndLine() { + List oldLines = [ + 'strip outside', + 'start', + 'replace1', + 'replace2', + 'end'] + + List newLines = XcodeTask.regexReplaceLines( + oldLines, true, /start/, /end/, ['replacement1', 'replacement2']) + + List expectedNewLines = [ + 'strip outside', + 'replacement1', + 'replacement2', + 'end'] + + assert expectedNewLines == newLines + } + + @Test + void testRegexReplacesLines_doubleStart() { + List oldLines = [ + 'start1', + 'start2', + 'end'] + + // With mistaken logic, insertLines can be inserted twice + // when two lines match startRegex before targetEndRegex is matched + List newLines = XcodeTask.regexReplaceLines( + oldLines, false, /start1|start2/, /end/, + [ + 'start1', + 'start2', + 'end' + ]) + + assert oldLines == newLines + } + @Test void testRegexInsertLines_afterStart() { List oldLines = [ @@ -332,16 +457,13 @@ class XcodeTaskTest { 'in-between', 'end'] - List insertLines = new ArrayList<>() - insertLines.add('line1') - insertLines.add('line2') List newLines = XcodeTask.regexInsertLines( - oldLines, true, /start/, /end/, insertLines) + oldLines, true, /start/, /end/, ['insert1', 'insert2']) List expectedNewLines = [ 'start', - 'line1', - 'line2', + 'insert1', + 'insert2', 'in-between', 'end'] assert expectedNewLines == newLines @@ -355,13 +477,13 @@ class XcodeTaskTest { 'end'] List newLines = XcodeTask.regexInsertLines( - oldLines, false, /start/, /end/, ['line1', 'line2']) + oldLines, false, /start/, /end/, ['insert1', 'insert2']) List expectedNewLines = [ 'start', 'in-between', - 'line1', - 'line2', + 'insert1', + 'insert2', 'end'] assert expectedNewLines == newLines } @@ -405,11 +527,112 @@ class XcodeTaskTest { 'end'] expectedException.expect(InvalidUserDataException.class) - expectedException.expectMessage('Failed to find endRegex: /no-match-end/') + expectedException.expectMessage('Failed to find targetEndRegex: /no-match-end/') XcodeTask.regexInsertLines(oldLines, false, /start/, /no-match-end/, new ArrayList<>()) } + @Test + void testRegexMatchesLine() { + List lines = [ + 'line1', + 'line2', + 'end'] + + assert XcodeTask.regexMatchesLine(lines, /^line1/) + assert XcodeTask.regexMatchesLine(lines, /[a-z]{4}2/) + assert ! XcodeTask.regexMatchesLine(lines, /no-match/) + } + + @Test + void testAddNewPodMethod_podInitDefault() { + List podfileLines = [ + "# Uncomment this line to define a global platform for your project", + "# platform :ios, '6.0'", + "", + "target 'IOS-APP' do", + "end"] + + List newPodfileLines = XcodeTask.addNewPodMethod(podfileLines, + ['', 'NEW-POD-METHOD1', 'NEW-POD-METHOD2', '']) + + List expectedPodfileLines = [ + "# Uncomment this line to define a global platform for your project", + "# platform :ios, '6.0'", + "", + "", + "NEW-POD-METHOD1", + "NEW-POD-METHOD2", + "", + "target 'IOS-APP' do", + "end"] + + assert expectedPodfileLines == newPodfileLines + } + + + @Test + void testAddNewPodMethod_podComplex() { + List podfileLines = [ + "source 'https://github.com/CocoaPods/Specs.git'", + "platform :ios, '9.0'", + "", + "# ignore all warnings from all pods", + "inhibit_all_warnings!", + "use_frameworks!", + "", + "pod 'AFNetworking'", + "", + "post_install do |installer|", + "LOTS OF COMPLEX RUBY", + "end"] + + List newPodfileLines = XcodeTask.addNewPodMethod(podfileLines, + ['', 'NEW-POD-METHOD1', 'NEW-POD-METHOD2', '']) + + List expectedPodfileLines = [ + "source 'https://github.com/CocoaPods/Specs.git'", + "platform :ios, '9.0'", + "", + "# ignore all warnings from all pods", + "inhibit_all_warnings!", + "use_frameworks!", + "", + "pod 'AFNetworking'", + "", + "", + "NEW-POD-METHOD1", + "NEW-POD-METHOD2", + "", + "post_install do |installer|", + "LOTS OF COMPLEX RUBY", + "end"] + + assert expectedPodfileLines == newPodfileLines + } + + @Test + void testAddNewPodMethod_noTargetFoundAppendsAtEnd() { + List podfileLines = [ + "# Uncomment this line to define a global platform for your project", + "# platform :ios, '6.0'", + ""] + + List newPodfileLines = XcodeTask.addNewPodMethod(podfileLines, + ['', 'NEW-POD-METHOD1', 'NEW-POD-METHOD2', '']) + + List expectedPodfileLines = [ + "# Uncomment this line to define a global platform for your project", + "# platform :ios, '6.0'", + "", + "", + "NEW-POD-METHOD1", + "NEW-POD-METHOD2", + ""] + + assert expectedPodfileLines == newPodfileLines + } + @Test void testWriteUpdatedPodfileIfNeeded_Needed_ThenNotNeeded() { @@ -430,16 +653,18 @@ class XcodeTaskTest { new File(podspecBuildDir + '/j2objc-PROJ-debug.podspec'), new File(podspecBuildDir + '/j2objc-PROJ-release.podspec'))) XcodeTask.writeUpdatedPodfileIfNeeded( - podspecDetailsList, xcodeTargetDetailsIosAppOnly, podfile) + podspecDetailsList, xcodeTargetDetailsIosAppOnly, false, podfile) // Verify modified Podfile List expectedLines = [ "#user comment", - "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", + '', + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", "def j2objc_PROJ", " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", "", "target 'IOS-APP' do", " platform :ios, '6.0.0'", @@ -451,7 +676,7 @@ class XcodeTaskTest { // Verify unmodified on second call // TODO: verify that file wasn't written a second time XcodeTask.writeUpdatedPodfileIfNeeded( - podspecDetailsList, xcodeTargetDetailsIosAppOnly, podfile) + podspecDetailsList, xcodeTargetDetailsIosAppOnly, false, podfile) readPodfileLines = podfile.readLines() assert expectedLines == readPodfileLines } @@ -466,14 +691,17 @@ class XcodeTaskTest { podfileLines, podspecDetailsProj, xcodeTargetDetailsIosAppOnly, + false, new File('/SRC/ios/Podfile')) List expectedPodfileLines = [ - "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", "def j2objc_PROJ", " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", "", "target 'IOS-APP' do", " platform :ios, '6.0.0'", @@ -486,10 +714,48 @@ class XcodeTaskTest { newPodfileLines, podspecDetailsProj, xcodeTargetDetailsIosAppOnly, + false, new File('/SRC/ios/Podfile')) assert expectedPodfileLines == newPodfileLines } + @Test + void testUpdatePodfile_podfileTargetsManualConfig() { + List podfileLines = [ + "target 'IOS-APP' do", + "end"] + + List newPodfileLines = XcodeTask.updatePodfile( + podfileLines, + podspecDetailsProj, + xcodeTargetDetailsEmpty , + true, + new File('/SRC/ios/Podfile')) + + List expectedPodfileLines = [ + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", + "def j2objc_PROJ", + " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", + " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", + "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", + "", + "target 'IOS-APP' do", + "end"] + assert expectedPodfileLines == newPodfileLines + + // Second time around is a no-op + newPodfileLines = XcodeTask.updatePodfile( + newPodfileLines, + podspecDetailsProj, + xcodeTargetDetailsEmpty, + true, + new File('/SRC/ios/Podfile')) + + assert expectedPodfileLines == newPodfileLines + } + @Test void testUpdatePodfile_complex() { List podfileLines = [ @@ -510,16 +776,19 @@ class XcodeTaskTest { podfileLines, podspecDetailsProj, xcodeTargetDetails, + false, new File('/SRC/ios/Podfile')) List expectedPodfileLines = [ "# user comment", "", - "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", "def j2objc_PROJ", " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", "", "target 'IOS-APP' do", " platform :ios, '6.0.0'", @@ -540,6 +809,7 @@ class XcodeTaskTest { newPodfileLines, podspecDetailsProj, xcodeTargetDetails, + false, new File('/SRC/ios/Podfile')) assert expectedPodfileLines == newPodfileLines } @@ -563,6 +833,7 @@ class XcodeTaskTest { new XcodeTask.XcodeTargetDetails( [], [], [], '6.0.0', '10.6.0', '1.0.0'), + false, null) } @@ -584,11 +855,132 @@ class XcodeTaskTest { new XcodeTask.XcodeTargetDetails( ['TARGET-DOES-NOT-EXIST'], [], [], '6.0.0', '10.6.0', '1.0.0'), + false, null) } @Test - void testUpdatePodMethods_basic() { + void testUpdatePodfile_xcodeManualConfigWithNoContent() { + List podfileLines = [] + + List newPodfileLines = XcodeTask.updatePodfile( + podfileLines, podspecDetailsProj, + xcodeTargetDetailsEmpty, + true, + new File('/SRC/ios/Podfile')) + + List expectedLines = [ + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", + "def j2objc_PROJ", + " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", + " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", + "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", + ""] + + assert expectedLines == newPodfileLines + } + + @Test + void testUpdatePodfile_xcodeManualConfigComplexPodfile() { + List podfileLines = [ + "source 'https://github.com/CocoaPods/Specs.git'", + "platform :ios, '7.0'", + "", + "# ignore all warnings from all pods", + "inhibit_all_warnings!", + "", + "pod 'AFNetworking'", + "pod 'OpenCV', '2.4.9.1'", + "pod 'Facebook-iOS-SDK'", + "", + "post_install do |installer|", + "target = installer.project.targets.find{|t| t.to_s == \"Pods-MagicalRecord\"}", + "target.build_configurations.each do |config|", + "s = config.build_settings['GCC_PREPROCESSOR_DEFINITIONS']", + "s = [ '\$(inherited)' ] if s == nil;", + "s.push('MR_ENABLE_ACTIVE_RECORD_LOGGING=0') if config.to_s == \"Debug\";", + "config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = s", + "end", + "end"] + + List newPodfileLines = XcodeTask.updatePodfile( + podfileLines, podspecDetailsProj, + xcodeTargetDetailsEmpty, + true, + new File('/SRC/ios/Podfile')) + + List expectedLines = [ + "source 'https://github.com/CocoaPods/Specs.git'", + "platform :ios, '7.0'", + "", + "# ignore all warnings from all pods", + "inhibit_all_warnings!", + "", + "pod 'AFNetworking'", + "pod 'OpenCV', '2.4.9.1'", + "pod 'Facebook-iOS-SDK'", + "", + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", + "def j2objc_PROJ", + " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", + " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", + "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", + "", + "post_install do |installer|", + "target = installer.project.targets.find{|t| t.to_s == \"Pods-MagicalRecord\"}", + "target.build_configurations.each do |config|", + "s = config.build_settings['GCC_PREPROCESSOR_DEFINITIONS']", + "s = [ '\$(inherited)' ] if s == nil;", + "s.push('MR_ENABLE_ACTIVE_RECORD_LOGGING=0') if config.to_s == \"Debug\";", + "config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = s", + "end", + "end"] + + assert expectedLines == newPodfileLines + } + + @Test + void testUpdatePodMethods_noChange() { + List podfileLines = [ + "# user comment", + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", + "def j2objc_PROJ", + " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", + " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", + "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", + "", + "target 'IOS-APP' do", + "end"] + + List newPodfileLines = XcodeTask.updatePodMethods( + podfileLines, + podspecDetailsProj, + new File('/SRC/ios/Podfile')) + + List expectedLines = [ + "# user comment", + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", + "def j2objc_PROJ", + " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", + " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", + "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", + "", + "target 'IOS-APP' do", + "end"] + + assert expectedLines == newPodfileLines + } + + @Test + void testUpdatePodMethods_upgradev051_oldest() { List podfileLines = [ "# user comment", "", @@ -607,31 +999,75 @@ class XcodeTaskTest { new File('/SRC/ios/Podfile')) List expectedLines = [ + "# user comment", + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", + "def j2objc_PROJ", + " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", + " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", + "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", + "", + "target 'IOS-APP' do", + "end"] + + assert expectedLines == newPodfileLines + } + + @Test + void testUpdatePodMethods_upgradev051_old() { + List podfileLines = [ "# user comment", "", "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", "def j2objc_PROJ", + " 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' do", + "end"] + + List newPodfileLines = XcodeTask.updatePodMethods( + podfileLines, + podspecDetailsProj, + new File('/SRC/ios/Podfile')) + + List expectedLines = [ + "# user comment", + "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", + "def j2objc_PROJ", " pod 'j2objc-PROJ-debug', :configuration => ['Debug'], :path => '../PROJ/BUILD'", " pod 'j2objc-PROJ-release', :configuration => ['Release'], :path => '../PROJ/BUILD'", "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", "", "target 'IOS-APP' do", "end"] assert expectedLines == newPodfileLines + + // Repeated update has no effect + List updatedAgainPodfileLines = XcodeTask.updatePodMethods( + newPodfileLines, + podspecDetailsProj, + new File('/SRC/ios/Podfile')) + + assert expectedLines == updatedAgainPodfileLines } @Test - void testUpdatePodMethods() { + void testUpdatePodMethods_multipleProjects() { List podfileLines = [ "# user comment", "", - "# J2ObjC Gradle Plugin - DO NOT MODIFY from this line to the first target", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", "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", - "", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", "", "target 'IOS-APP' do", " j2objc_DELETED_BY_ANOTHER_METHOD", @@ -652,16 +1088,16 @@ class XcodeTaskTest { List expectedLines = [ "# user comment", "", - "# J2ObjC Gradle Plugin - DO NOT MODIFY from here to the first target", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY START - can be moved as a block", "def j2objc_PROJA", " pod 'j2objc-PROJA-debug', :configuration => ['Debug'], :path => '../PROJA/BUILD'", " pod 'j2objc-PROJB-release', :configuration => ['Release'], :path => '../PROJA/BUILD'", "end", - "", "def j2objc_PROJB", " pod 'j2objc-PROJB-debug', :configuration => ['Debug'], :path => '../PROJB/BUILD'", " pod 'j2objc-PROJB-release', :configuration => ['Release'], :path => '../PROJB/BUILD'", "end", + "# J2ObjC Gradle Plugin - PodMethods - DO NOT MODIFY END", "", "target 'IOS-APP' do", " j2objc_DELETED_BY_ANOTHER_METHOD",