Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Config to only add j2objc_shared method to Podfile #565

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,16 @@ class J2objcConfig {
appendArgs(this.xcodeTargetsWatchos, 'xcodeTargetsWatchos', false, xcodeTargetsWatchos)
}

/**
* Allows manual config of 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. When used, you must also set xcodeTargets{Ios|Osx|Watchos)
* to empty.
*/
boolean xcodeTargetsManualConfig = false


protected boolean finalConfigured = false
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,15 @@ class XcodeTask extends DefaultTask {
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 sourceLineRegex = /^source 'http.*Specs.git'$/ // https://guides.cocoapods.org/syntax/podfile.html#podfile
public static final String platformLineRegex = /^platform :.*/

@Input @Optional
String getXcodeProjectDir() { return J2objcConfig.from(project).xcodeProjectDir }

@Input
boolean getXcodeTargetsManualConfig() { return J2objcConfig.from(project).xcodeTargetsManualConfig }

boolean isTaskActive() { return getXcodeProjectDir() != null }

@Input
Expand Down Expand Up @@ -200,7 +205,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()
Expand Down Expand Up @@ -316,6 +321,41 @@ class XcodeTask extends DefaultTask {
return result
}

/**
* Replaces lines between two regexps
*/
@VisibleForTesting
static List<String> regexReplaceLines(List<String> podfileLines,
String startRegex, String endRegex, List<String> newPodFileLines) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a variation of the regexInsertLines method. Just extend regexInsertLines, to add a boolean replace parameter. If true, it should skip copying the old lines. It avoid having duplication of similar code.

List<String> result = new ArrayList<>()
boolean active = false
boolean endOfRegex = false

for (line in podfileLines) {
if (!active && (line =~ startRegex)) {
active = true
result.addAll(newPodFileLines)
}
if (active && (line =~ endRegex)) {
active = false
endOfRegex = true
}
if (!active && !endOfRegex) {
result.add(line)
}

if(endOfRegex){
endOfRegex=false
}
}
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.
*
Expand Down Expand Up @@ -368,20 +408,131 @@ class XcodeTask extends DefaultTask {
return result
}

/**
* Searches for the line where to insert new methods into the podfile
*/
@VisibleForTesting
static int findInsertLine(List<String> podfileLines) {

boolean noneCommentLineFound =false
boolean platformStatementFound =false
boolean sourceStatementFound =false

int counter = 0
int insertLineNumber = 0
for (line in podfileLines) {

// if a target statement is found. insert before this statement
if(line =~ targetStartRegex) {
return counter
}

counter++

// else insert after platform statement (platform :ios, '6.0')
if(platformStatementFound) continue
if(line =~ platformLineRegex) {
insertLineNumber = counter
platformStatementFound = true
continue
}

// else insert after platform statement (source 'https://github.com/CocoaPods/Specs.git')
if(sourceStatementFound) continue
if(line =~ sourceLineRegex) {
insertLineNumber = counter
sourceStatementFound = true
continue
}

// else insert at first none commented line
if(noneCommentLineFound) continue

if(!line.trim().startsWith("#")){
insertLineNumber = counter-1
noneCommentLineFound = true
}

}

return insertLineNumber
}

/**
* Insert new lines in to podfile at a certain line
* @param surroundWithEmptyLines regulates if inserted lines should be surrounded with empty ones
*
*/
@VisibleForTesting
static List<String> insertAtLineNumber(List<String> podfileLines,List<String> insertLines, int line, boolean surroundWithEmptyLines) {
List<String> result = new ArrayList<>()
result.addAll(podfileLines.subList(0,line))

assert podfileLines.size() >= line

boolean insertAtTheEnd = podfileLines.size()==line
boolean insertAtTheBeginning = line==0

if(surroundWithEmptyLines){
if(!insertAtTheBeginning){
if(!isBlankLine(podfileLines,line-1)){
result.add('')
}
if(isBlankLine(podfileLines,line)){
line++
}
}
if(!insertAtTheEnd){
insertLines.add('')
if(isBlankLine(podfileLines,line)){
line++
}
}
}
result.addAll(insertLines)
if(!insertAtTheEnd){
result.addAll(podfileLines.subList(line,podfileLines.size()))
}
return result
}

static boolean isBlankLine(List<String> podfileLines,int line){
if(line>=podfileLines.size()){
return false
}
return podfileLines.get(line).trim()==''
}

/**
* Checks if Podfile contains a certain regex
*
*/
@VisibleForTesting
static boolean containsRegex(List<String> podfileLines, String startRegex) {

for (line in podfileLines) {
if ((line =~ startRegex).find()) {
return true
}
}
return false
}

/**
* Modify in place the existing podfile.
*/
@VisibleForTesting
static void writeUpdatedPodfileIfNeeded(
List<PodspecDetails> podspecDetailsList,
XcodeTargetDetails xcodeTargetDetails,
boolean xcodeTargetsManualConfig,
File podfile) {

List<String> oldPodfileLines = podfile.readLines()
List<String> newPodfileLines = new ArrayList<String>(oldPodfileLines)

newPodfileLines = updatePodfile(
newPodfileLines, podspecDetailsList, xcodeTargetDetails, podfile)
newPodfileLines, podspecDetailsList, xcodeTargetDetails, xcodeTargetsManualConfig, podfile)

// Write file only if it's changed
if (!oldPodfileLines.equals(newPodfileLines)) {
Expand All @@ -394,33 +545,46 @@ class XcodeTask extends DefaultTask {
List<String> podfileLines,
List<PodspecDetails> podspecDetailsList,
XcodeTargetDetails xcodeTargetDetails,
boolean xcodeTargetsManualConfig,
File podfile) {

List<String> podfileTargets = extractXcodeTargets(podfileLines)
verifyTargets(xcodeTargetDetails.xcodeTargetsIos, podfileTargets, 'xcodeTargetsIos')
verifyTargets(xcodeTargetDetails.xcodeTargetsOsx, podfileTargets, 'xcodeTargetsOsx')
verifyTargets(xcodeTargetDetails.xcodeTargetsWatchos, podfileTargets, 'xcodeTargetsWatchos')
List<String> newPodfileLines = podfileLines;

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 must all be blank when using xcodeTargetsManualConfig.\n" +
"Please update j2objcConfig by removing xcodeTargetsIos, xcodeTargetsOsx & xcodeTargetsWatchos")
}
} else {
// xcodeTargetsManualConfig = false (default)
List<String> podfileTargets = extractXcodeTargets(podfileLines)
verifyTargets(xcodeTargetDetails.xcodeTargetsIos, podfileTargets, 'xcodeTargetsIos')
verifyTargets(xcodeTargetDetails.xcodeTargetsOsx, podfileTargets, 'xcodeTargetsOsx')
verifyTargets(xcodeTargetDetails.xcodeTargetsWatchos, podfileTargets, 'xcodeTargetsWatchos')

if (xcodeTargetsAllEmpty) {
// 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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add additional line:

This check can be disabled with "xcodeTargetsManualConfig = true"

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrite logic so that it forces xcode targets to be empty when doing manual config. It should be either or when configuring this.

boolean xcodeTargetsAllEmpty =
        xcodeTargetDetails.xcodeTargetsIos.isEmpty() &&
        xcodeTargetDetails.xcodeTargetsOsx.isEmpty() &&
        xcodeTargetDetails.xcodeTargetsWatchos.isEmpty()

if (xcodeTargetsManualConfig) {
    if (!xcodeTargetsAllEmpty) {
        throw new InvalidUserDataException(
                "Xcode targets must all be blank when using xcodeTargetsManualConfig.\n" +
                "Please update j2objcConfig by removing xcodeTargetsIos, xcodeTargetsOsx & xcodeTargetsWatchos")
    }
} else {
    // xcodeTargetsManualConfig = false  (default)
    List<String> podfileTargets = extractXcodeTargets(podfileLines)
    verifyTargets(...
    if (xcodeTargetsAllEmpty) {
        existing exception
    }
}

newPodfileLines = updatePodfileTargets(newPodfileLines, podspecDetailsList, xcodeTargetDetails)
}

// update pod methods
List<String> newPodfileLines = updatePodMethods(podfileLines, podspecDetailsList, podfile)

// update pod targets
newPodfileLines = updatePodfileTargets(newPodfileLines, podspecDetailsList, xcodeTargetDetails)
newPodfileLines = updatePodMethods(newPodfileLines, podspecDetailsList, podfile)

return newPodfileLines
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the verityTargets method below (I have to comment on this line as I can't comment in the review on a line that hasn't been modified). Within that method, add another line to explain how to handle a complex Podfile. My supposition is that in that scenario, this is where it will fail, so it's the most useful spot to add an explanation.

private static verifyTargets(List<String> xcodeTargets, List<String> 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("', '")}'\n" +
                    "NOTE: if your Podfile is too complex, you may need to use xcodeTargetsManualConfig")
        }
    }
}

Expand All @@ -430,7 +594,8 @@ class XcodeTask extends DefaultTask {
if (! podfileTargets.contains(xcodeTarget)) {
throw new InvalidUserDataException(
"Invalid j2objcConfig { $xcodeTargetsName '$xcodeTarget' }\n" +
"Must be one of the valid targets: '${podfileTargets.join("', '")}'")
"Must be one of the valid targets: '${podfileTargets.join("', '")}'\n" +
"NOTE: if your Podfile is too complex, you may need to use xcodeTargetsManualConfig")
}
}
}
Expand All @@ -439,10 +604,6 @@ class XcodeTask extends DefaultTask {
static List<String> updatePodMethods(
List<String> podfileLines, List<PodspecDetails> podspecDetailsList, File podfile) {

// strip all old methods
// Note: use of preserveEndLine=true so that the targetStartRegex isn't removed
List<String> newPodfileLines =
regexStripLines(podfileLines, false, podMethodStartRegex, targetStartRegex, /.*/)

// create new methods
List<String> insertLines = new ArrayList<>()
Expand All @@ -452,10 +613,16 @@ class XcodeTask extends DefaultTask {
insertLines.add('')
}

// insert new methods immediately before first target
newPodfileLines = regexInsertLines(newPodfileLines, false, /.*/, targetStartRegex, insertLines)
// remove last empty line
insertLines.remove(insertLines.size()-1)

if(containsRegex(podfileLines,podMethodStartRegex)){
return regexReplaceLines(podfileLines, podMethodStartRegex, endRegex, insertLines)
}else {
int lineNr = findInsertLine(podfileLines)
return insertAtLineNumber(podfileLines, insertLines, lineNr, true)
}

return newPodfileLines
}

@VisibleForTesting
Expand Down
Loading