Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-28267

When building IPA - Expand 'Output directory' with extra variables

    • Icon: New Feature New Feature
    • Resolution: Unresolved
    • Icon: Major Major
    • xcode-plugin
    • None

      It would be a very helpful feature to expand Output directory with extra variables.

      Current behavior:
      .ipa filename pattern: ipaname_${SHORT_VERSION}
      Output directory: Builds/APPFOLDER/${SHORT_VERSION}
      Filesystem:
      .ipa filename pattern: ipaname_1.0.4.ipa
      Output directory: Builds/APPFOLDER/${SHORT_VERSION}

      Desired behavior
      .ipa filename pattern: ipaname_${SHORT_VERSION}
      Output directory: Builds/APPFOLDER/${SHORT_VERSION}
      Filesystem:
      .ipa filename pattern: ipaname_1.0.4.ipa
      Output directory: Builds/APPFOLDER/1.0.4

          [JENKINS-28267] When building IPA - Expand 'Output directory' with extra variables

          lacostej added a comment -

          If I read the code correctly, right now the code does the following

          1. prepare the ipaOutputPath
          2. remove existing ipas and dsyms in the global ipaOutputPath
          3. for each app
          a. build the ipa into the ipaOutputPath

          Your proposal requires us to make the change of making the ipaOutputPath to be app specific. Cleaning in the loop will not work in the same way.

          I don't use projects that contain multiple apps. I would be happy to know more about those use cases to make sure they don't conflict with this change.

          lacostej added a comment - If I read the code correctly, right now the code does the following 1. prepare the ipaOutputPath 2. remove existing ipas and dsyms in the global ipaOutputPath 3. for each app a. build the ipa into the ipaOutputPath Your proposal requires us to make the change of making the ipaOutputPath to be app specific. Cleaning in the loop will not work in the same way. I don't use projects that contain multiple apps. I would be happy to know more about those use cases to make sure they don't conflict with this change.

          Igor M added a comment -

          The reason I needed it for our environment is that we maintain one code base but have multiple targets.
          Each target caters specific features to each client, and they each have their own version of the app that is also incremented by xcode.

          When I build the project with jenkins, all targets are built at once each target is a XCODE plugin.
          Each target has its own folder and each folder has version.
          The output folders are located on the network drive so our qa team can go and grab the latest build and also take previous ones if needed.
          They don't have access to jenkins interface.

          I have actually modified the code a while back and am attaching a diff.
          It works pretty well for our needs.

          Igor M added a comment - The reason I needed it for our environment is that we maintain one code base but have multiple targets. Each target caters specific features to each client, and they each have their own version of the app that is also incremented by xcode. When I build the project with jenkins, all targets are built at once each target is a XCODE plugin. Each target has its own folder and each folder has version. The output folders are located on the network drive so our qa team can go and grab the latest build and also take previous ones if needed. They don't have access to jenkins interface. I have actually modified the code a while back and am attaching a diff. It works pretty well for our needs.

          Igor M added a comment -

          Please see if this is of any use, or acceptable.

          Igor M added a comment - Please see if this is of any use, or acceptable.

          lacostej added a comment -

          Thanks Igor

          If you convert it into a PR, I'll be able to comment right in. My first comment would be to say that can the

          +                listener.getLogger().println(Messages.XCodeBuilder_cleaningIPA());
          +                for (FilePath path : ipaOutputPath.list("*.ipa")) {
          +                    path.delete();
          

          cause freshly generated ipas to be deleted (when inside a loop, if the outputPath is the same for all multiple apps). That was my concern above.

          lacostej added a comment - Thanks Igor If you convert it into a PR, I'll be able to comment right in. My first comment would be to say that can the + listener.getLogger().println(Messages.XCodeBuilder_cleaningIPA()); + for (FilePath path : ipaOutputPath.list( "*.ipa" )) { + path.delete(); cause freshly generated ipas to be deleted (when inside a loop, if the outputPath is the same for all multiple apps). That was my concern above.

          Igor M added a comment -

          Hi,

          I am sorry I am not entirely understanding your comment, PR?
          The code I provided actually takes care of that stuff and directory that is cleaned is only the one that is expanded by the new variables so stuff works well. Here is the code just in case:

          # This patch file was generated by NetBeans IDE
          # It uses platform neutral UTF-8 encoding and \n newlines.
          --- <html>XCodeBuilder.java (<b>Feb 27, 2015 11:50:18 PM</b>)</html>
          +++ <html><b>Current File</b></html>
          @@ -621,29 +621,7 @@
                           return false;                
                       }
                       
          -            // clean IPA
          -            FilePath ipaOutputPath = null;
          -            if (ipaOutputDirectory != null && ! StringUtils.isEmpty(ipaOutputDirectory)) {
          -            	ipaOutputPath = buildDirectory.child(ipaOutputDirectory);
                       	
          -            	// Create if non-existent
          -            	if (! ipaOutputPath.exists()) {
          -            		ipaOutputPath.mkdirs();
          -            	}
          -            }
          -            
          -            if (ipaOutputPath == null) {
          -            	ipaOutputPath = buildDirectory;
          -            }
          -            
          -            listener.getLogger().println(Messages.XCodeBuilder_cleaningIPA());
          -            for (FilePath path : ipaOutputPath.list("*.ipa")) {
          -                path.delete();
          -            }
          -            listener.getLogger().println(Messages.XCodeBuilder_cleaningDSYM());
          -            for (FilePath path : ipaOutputPath.list("*-dSYM.zip")) {
          -                path.delete();
          -            }
                       // packaging IPA
                       listener.getLogger().println(Messages.XCodeBuilder_packagingIPA());
                       List<FilePath> apps = buildDirectory.list(new AppFileFilter());
          @@ -680,8 +658,52 @@
                          		return false;
                          	}
           
          +                //
          +                
          +                // clean IPA
          +                FilePath ipaOutputPath = null;
          +                if (ipaOutputDirectory != null && ! StringUtils.isEmpty(ipaOutputDirectory)) {                    
                           String lastModified = new SimpleDateFormat("yyyy.MM.dd").format(new Date(app.lastModified()));
           
          +                    // If custom .ipa name pattern has been provided, use it and expand version and build date variables
          +                    
          +                            EnvVars customVars = new EnvVars(
          +                                    "BASE_NAME", app.getBaseName().replaceAll(" ", "_"),
          +                                    "VERSION", version,
          +                                    "SHORT_VERSION", shortVersion,
          +                                    "BUILD_DATE", lastModified
          +                            );
          +                        ipaOutputDirectory = customVars.expand(ipaOutputDirectory);
          +                    
          +                    
          +                    ipaOutputPath = buildDirectory.child(ipaOutputDirectory);
          +                    
          +                    // Create if non-existent
          +                    if (! ipaOutputPath.exists()) {
          +                            ipaOutputPath.mkdirs();
          +                    }
          +                }
          +
          +                if (ipaOutputPath == null) {
          +                    ipaOutputPath = buildDirectory;
          +                }
          +
          +                listener.getLogger().println(Messages.XCodeBuilder_cleaningIPA());
          +                for (FilePath path : ipaOutputPath.list("*.ipa")) {
          +                    path.delete();
          +                }
          +                listener.getLogger().println(Messages.XCodeBuilder_cleaningDSYM());
          +                for (FilePath path : ipaOutputPath.list("*-dSYM.zip")) {
          +                    path.delete();
          +                }
          +                
          +                //
          +                
          +                
          +
          +
          +                String lastModified = new SimpleDateFormat("yyyy.MM.dd").format(new Date(app.lastModified()));
          +
                           String baseName = app.getBaseName().replaceAll(" ", "_") + (shortVersion.isEmpty() ? "" : "-" + shortVersion) + (version.isEmpty() ? "" : "-" + version);
                           // If custom .ipa name pattern has been provided, use it and expand version and build date variables
                           if (! StringUtils.isEmpty(ipaName)) {
          
          

          Igor M added a comment - Hi, I am sorry I am not entirely understanding your comment, PR? The code I provided actually takes care of that stuff and directory that is cleaned is only the one that is expanded by the new variables so stuff works well. Here is the code just in case: # This patch file was generated by NetBeans IDE # It uses platform neutral UTF-8 encoding and \n newlines. --- <html>XCodeBuilder.java (<b>Feb 27, 2015 11:50:18 PM</b>)</html> +++ <html><b>Current File</b></html> @@ -621,29 +621,7 @@ return false ; } - // clean IPA - FilePath ipaOutputPath = null ; - if (ipaOutputDirectory != null && ! StringUtils.isEmpty(ipaOutputDirectory)) { - ipaOutputPath = buildDirectory.child(ipaOutputDirectory); - // Create if non-existent - if (! ipaOutputPath.exists()) { - ipaOutputPath.mkdirs(); - } - } - - if (ipaOutputPath == null ) { - ipaOutputPath = buildDirectory; - } - - listener.getLogger().println(Messages.XCodeBuilder_cleaningIPA()); - for (FilePath path : ipaOutputPath.list( "*.ipa" )) { - path.delete(); - } - listener.getLogger().println(Messages.XCodeBuilder_cleaningDSYM()); - for (FilePath path : ipaOutputPath.list( "*-dSYM.zip" )) { - path.delete(); - } // packaging IPA listener.getLogger().println(Messages.XCodeBuilder_packagingIPA()); List<FilePath> apps = buildDirectory.list( new AppFileFilter()); @@ -680,8 +658,52 @@ return false ; } + // + + // clean IPA + FilePath ipaOutputPath = null ; + if (ipaOutputDirectory != null && ! StringUtils.isEmpty(ipaOutputDirectory)) { String lastModified = new SimpleDateFormat( "yyyy.MM.dd" ).format( new Date(app.lastModified())); + // If custom .ipa name pattern has been provided, use it and expand version and build date variables + + EnvVars customVars = new EnvVars( + "BASE_NAME" , app.getBaseName().replaceAll( " " , "_" ), + "VERSION" , version, + "SHORT_VERSION" , shortVersion, + "BUILD_DATE" , lastModified + ); + ipaOutputDirectory = customVars.expand(ipaOutputDirectory); + + + ipaOutputPath = buildDirectory.child(ipaOutputDirectory); + + // Create if non-existent + if (! ipaOutputPath.exists()) { + ipaOutputPath.mkdirs(); + } + } + + if (ipaOutputPath == null ) { + ipaOutputPath = buildDirectory; + } + + listener.getLogger().println(Messages.XCodeBuilder_cleaningIPA()); + for (FilePath path : ipaOutputPath.list( "*.ipa" )) { + path.delete(); + } + listener.getLogger().println(Messages.XCodeBuilder_cleaningDSYM()); + for (FilePath path : ipaOutputPath.list( "*-dSYM.zip" )) { + path.delete(); + } + + // + + + + + String lastModified = new SimpleDateFormat( "yyyy.MM.dd" ).format( new Date(app.lastModified())); + String baseName = app.getBaseName().replaceAll( " " , "_" ) + (shortVersion.isEmpty() ? "" : " - " + shortVersion) + (version.isEmpty() ? " " : " -" + version); // If custom .ipa name pattern has been provided, use it and expand version and build date variables if (! StringUtils.isEmpty(ipaName)) {

          Igor M added a comment -

          So basically all i am doing is expanding ipa path and ipa name with new variables, so old functionality shouldn't change.

          Igor M added a comment - So basically all i am doing is expanding ipa path and ipa name with new variables, so old functionality shouldn't change.

          lacostej added a comment -

          1. you move the delete() calls into a for loop (for all apps).
          2. the ipaOutputPath can be the same for all apps
          3. on second iteration of the for loop the delete() call might delete the freshly generated ipa ??

          Am I missing something ?

          Do you generate multiple ipas in your builds ?

          lacostej added a comment - 1. you move the delete() calls into a for loop (for all apps). 2. the ipaOutputPath can be the same for all apps 3. on second iteration of the for loop the delete() call might delete the freshly generated ipa ?? Am I missing something ? Do you generate multiple ipas in your builds ?

          Igor M added a comment -

          Hey,

          Hah, I haven't seen that code since February

          1. If you look at the diff you will see that the loop that deletes ipas and dsyms didnt change, I just added expansion of the variables.
          It would probably be easier to apply the patch on your local machine and see what lines changed. I think it shows so many lines because my ide removed some white space.

          2. In my case when I create project I add multiple xcodebuild steps. Each one has a different target and its own path.
          3. So the loop (that's always been there) will only delete ipa within a given folder ex. Builds/TargetNameClient1/1.0.4/*.ipa
          My output build folder would looke like this:
          Builds/TargetNameClient1/1.0.4/*.ipa
          Builds/TargetNameClient1/1.0.5/*.ipa
          Builds/TargetNameClient1/1.0.6/*.ipa
          Builds/TargetNameClient2/1.0.4/*.ipa

          Does this make sense?

          Igor M added a comment - Hey, Hah, I haven't seen that code since February 1. If you look at the diff you will see that the loop that deletes ipas and dsyms didnt change, I just added expansion of the variables. It would probably be easier to apply the patch on your local machine and see what lines changed. I think it shows so many lines because my ide removed some white space. 2. In my case when I create project I add multiple xcodebuild steps. Each one has a different target and its own path. 3. So the loop (that's always been there) will only delete ipa within a given folder ex. Builds/TargetNameClient1/1.0.4/*.ipa My output build folder would looke like this: Builds/TargetNameClient1/1.0.4/*.ipa Builds/TargetNameClient1/1.0.5/*.ipa Builds/TargetNameClient1/1.0.6/*.ipa Builds/TargetNameClient2/1.0.4/*.ipa Does this make sense?

          lacostej added a comment -

          1. with regard to patches, we prefer submissions via Pull Requests. It makes it easier to view the diff, view the resulting file and comment or apply right away.

          2. WRT the loop, I am thinking of the following case:

          • same output dir. outputDir/
          • 2 apps, resulting in 2 ipas: outputDir/app1.ipa outputDir/app2.ipa

          Previous behaviour:

          • after first loop: outputDir/app1.ipa
          • after second loop: outputDir/app1.ipa, outputDir/app2.ipa

          Proposed behaviour:

          • after first loop: outputDir/app1.ipa
          • after second loop: outputDir/app2.ipa

          the delete() would delete ipa1 if in same directory. Wouldn't it ?

          Please make a PR it will be easier for both of us

          lacostej added a comment - 1. with regard to patches, we prefer submissions via Pull Requests. It makes it easier to view the diff, view the resulting file and comment or apply right away. 2. WRT the loop, I am thinking of the following case: same output dir. outputDir/ 2 apps, resulting in 2 ipas: outputDir/app1.ipa outputDir/app2.ipa Previous behaviour: after first loop: outputDir/app1.ipa after second loop: outputDir/app1.ipa, outputDir/app2.ipa Proposed behaviour: after first loop: outputDir/app1.ipa after second loop: outputDir/app2.ipa the delete() would delete ipa1 if in same directory. Wouldn't it ? Please make a PR it will be easier for both of us

          Igor M added a comment -

          Igor M added a comment - lacostej hi I made a PR for this issue. https://github.com/jenkinsci/xcode-plugin/pull/79

            Unassigned Unassigned
            imekhtiev Igor M
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: