I am trying to create CI/CD pipeline, for Flutter iOS app using azure-pipelines.yml file. Everything is OK in android studio and Xcode. But when I try to build ios app in Azure DevOps I get this error:

/usr/local/lib/ruby/gems/2.7.0/bin/pod --version
1.11.3
/usr/local/lib/ruby/gems/2.7.0/bin/pod install --repo-update
[!] No `Podfile' found in the project directory.
##[error]The process '/usr/local/lib/ruby/gems/2.7.0/bin/pod' failed with exit code 1
##[error]The 'pod' command failed with error: The process '/usr/local/lib/ruby/gems/2.7.0/bin/pod' failed with exit code 1
Finishing: CocoaPods

My azure-pipelines.yml file is:

- task: Insta[email protected]
  inputs:
    certSecureFile: '$(p12FileName)'
    certPwd: '$(p12Password)'
    keychain: 'temp'
    deleteCert: true  

- task: [email protected]
  displayName: "Install provisioning file"
  inputs:
    provisioningProfileLocation: 'secureFiles'
    provProfileSecureFile: '$(provisioningProfile)'

- task: [email protected]
  inputs:
    forceRepoUpdate: true

- task: [email protected]
  displayName: 'Xcode task'
  inputs:
    actions: 'build'
    sdk: 
    configuration: 'Release'
    xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
    xcodeVersion: '12'
    packageApp: true
    signingOption: 'manual'
    signingIdentity: '$(APPLE_CERTIFICATE_SIGNING_IDENTITY)'
    provisioningProfileUuid: '$(APPLE_PROV_PROFILE_UUID)'

- task: [email protected]
  displayName: "Install Flutter SDK"
  inputs:
    mode: 'auto'
    channel: 'stable'
    version: 'latest'

- task: [email protected]
  displayName: "Build application"
  inputs:
    target: ipa
    projectDirectory: '$(Build.SourcesDirectory)'
    exportOptionsPlist: 'ios/exportOptions.plist'

and this is myPodfile:

platform :ios, '12.0'

ENV['COCOAPODS_DISABLE_STATS'] = 'true'

project 'Runner', {
  'Debug' => :debug,
  'Profile' => :release,
  'Release' => :release,
}

def flutter_root
  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
  unless File.exist?(generated_xcode_build_settings_path)
    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
  end

  File.foreach(generated_xcode_build_settings_path) do |line|
    matches = line.match(/FLUTTER_ROOT\=(.*)/)
    return matches[1].strip if matches
  end
  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end

require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)

flutter_ios_podfile_setup

def flutter_install_ios_plugin_pods(ios_application_path = nil)
  ios_application_path ||= File.dirname(defined_in_file.realpath) if self.respond_to?(:defined_in_file)
  raise 'Could not find iOS application path' unless ios_application_path

  symlink_dir = File.expand_path('.symlinks', ios_application_path)
  system('rm', '-rf', symlink_dir) # Avoid the complication of dependencies like FileUtils.

  symlink_plugins_dir = File.expand_path('plugins', symlink_dir)
  system('mkdir', '-p', symlink_plugins_dir)

  plugins_file = File.join(ios_application_path, '..', '.flutter-plugins-dependencies')
  plugin_pods = flutter_parse_plugins_file(plugins_file)
  plugin_pods.each do |plugin_hash|
    plugin_name = plugin_hash['name']
    plugin_path = plugin_hash['path']
    if (plugin_name && plugin_path)
      symlink = File.join(symlink_plugins_dir, plugin_name)
      File.symlink(plugin_path, symlink)

      if plugin_name == 'flutter_ffmpeg'
          pod 'flutter_ffmpeg/full-lts', :path => File.join('.symlinks', 'plugins', plugin_name, 'ios')
      else
          pod plugin_name, :path => File.join('.symlinks', 'plugins', plugin_name, 'ios')
      end
    end
  end
end

target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end

post_install do |installer|
  installer.pods_project.build_configurations.each do |config|
    config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = ''
    config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
    config.build_settings['CODE_SIGNING_REQUIRED'] = 'NO'
  end
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
        config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = ''
        config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
        config.build_settings['CODE_SIGNING_REQUIRED'] = 'NO'
    end
  end
end

If I add

workingDirectory: '$(Build.SourcesDirectory)/ios'

I will get this error message:

"Invalid Podfile file: /Users/runner/work/1/s/ios/Flutter/Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first."


Solution 1: Ging Yuan-MSFT

According to the error message, it seems the podfile cannot be found under the repository directory.

You could check the podfile location and try to specific the workingDirectory and projectDirectory in the [email protected] task to check the status.

For more information, you could refer to: https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/package/cocoapods?view=azure-devops#yaml-snippet


Solution 2: M Karimi

Finally, I found the solution:

- stage: iOSStage
  dependsOn: []
  displayName: iOS
  condition: always()
  jobs:

  - job: iOSJob
    displayName: iOS
    steps: 

    - task: [email protected]
      displayName: "Install Flutter SDK"
      inputs:
        mode: 'auto'
        channel: 'stable'
        version: 'latest'

    - task: [email protected]
      displayName: 'Install Apple Certificate'
      inputs:
        certSecureFile: '$(p12FileName)'
        certPwd: '$(p12Password)'
        keychain: 'temp'
        deleteCert: true  

    - task: [email protected]
      displayName: "Install provisioning file"
      inputs:
        provisioningProfileLocation: 'secureFiles'
        provProfileSecureFile: '$(provisioningProfile)'

    - task: [email protected]
      displayName: '[Flutter] Configure Flutter'
      inputs:
        targetType: 'inline'
        script: |
          $(FlutterToolPath)/flutter doctor -v
          $(FlutterToolPath)/flutter config --no-analytics

    - task: [email protected]
      displayName: '[Flutter] Build project environment'
      inputs:
        targetType: 'inline'
        script: |
          $(FlutterToolPath)/flutter pub get
          $(FlutterToolPath)/flutter pub global activate junitreport

    - task: [email protected]
      displayName: '[Flutter] Flutter coverage'
      inputs:
        targetType: 'inline'
        script: |
          pip install lcov_cobertura
          $(FlutterToolPath)/flutter test --coverage
          python -m lcov_cobertura coverage/lcov.info -o coverage/coverage.xml

    - task: [email protected]
      displayName: '[Flutter] Flutter build'
      inputs:
        targetType: 'inline'
        script: |
          $(FlutterToolPath)/flutter build ios --release --no-codesign --build-number $(Build.BuildId)

    - task: [email protected]
      inputs:
        workingDirectory: '$(Build.SourcesDirectory)/ios/Flutter'
        forceRepoUpdate: true

    - task: [email protected]
      displayName: 'Xcode task'
      inputs:
        actions: 'build'
        sdk: '$(sdk)'
        configuration: '$(configuration)'
        scheme: '$(scheme)'
        xcWorkspacePath: ios/Runner.xcworkspace
        signingOption: 'manual'
        signingIdentity: $(APPLE_CERTIFICATE_SIGNING_IDENTITY)
        provisioningProfileUuid: $(APPLE_PROV_PROFILE_UUID)
        packageApp: true
        workingDirectory: 'ios'
        exportOptions: 'auto'
        exportMethod: 'ad-hoc'
        archivePath: 'output/$(sdk)/$(configuration)/Runner.xcarchive'
        exportPath: 'output/$(sdk)/$(configuration)'
        useXcpretty: 'false'
        args: '-verbose'

    - task: [email protected]
      displayName: 'Copy app to staging directory'
      inputs:
        Contents: '**/*.ipa'
        TargetFolder: '$(build.artifactStagingDirectory)'
        OverWrite: true

    - task: [email protected]
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'drop'
        publishLocation: 'Container'