apollographql / apollo-ios

📱  A strongly-typed, caching GraphQL client for iOS, written in Swift.
https://www.apollographql.com/docs/ios/
MIT License
3.88k stars 726 forks source link

No such module 'SchemaName' in generated .graphql.swift files #2662

Closed jordanhbuiltbyhq closed 1 year ago

jordanhbuiltbyhq commented 1 year ago

Bug report

I'm running into an issue where I successfully generated the files, but when I build my project I get an error No such module 'SchemaName' in the generated .graphql.swift files.

Versions

Steps to reproduce

  1. Download the schema from Apollo Studio and rename its extension to graphqls
  2. Create an operations.graphql file with queries and mutations
  3. Add these files into a new directory I created: AppName/GraphQL
  4. cd into that location
  5. Execute ../../Pods/Apollo/apollo-ios-cli init --schema-name MyAppAPI (see generated code below)
  6. Execute ../../Pods/Apollo/apollo-ios-cli generate
  7. Drag and drop the GraphQL folder into Xcode, choosing Create groups, and select the targets to add them to (there's two app targets in this project)
  8. I then unchecked the apollo-codegen-config.json, myapp@main.graphqls, and operations.graphql files from the targets, and verified all the others are checked for the targets including files in the MyAppAPI folder
  9. Build the app

Result: A build error occurs in a generated mutation file:

// @generated
// This file was automatically generated and should not be edited.

@_exported import Apollo
import MyAppAPI //FIXME: This is where the error is: No such module 'MyAppAPI'

public class AddRehearsalCodeMutation: GraphQLMutation {

Further details

Note I'm building a macOS app targeting 10.15+ and using macOS Ventura on an M2 machine.

Config file

{
  "schemaName" : "MyAppAPI",
  "options" : {
    "cocoapodsCompatibleImportStatements" : true
  },
  "input" : {
    "operationSearchPaths" : [
      "**/*.graphql"
    ],
    "schemaSearchPaths" : [
      "**/*.graphqls"
    ]
  },
  "output" : {
    "testMocks" : {
      "none" : {
      }
    },
    "schemaTypes" : {
      "path" : "./MyAppAPI",
      "moduleType" : {
        "other" : {
        }
      }
    },
    "operations" : {
      "relative" : {
      }
    }
  }
}
StarLard commented 1 year ago

From the documentation for the other schema type:

The name of the target for your schema module must be the schemaName provided in your configuration, as this will be used in the import statements of generated operation files.

You are passing MyAppAPI as your schemaName and thus the library is generating import MyAppAPI statements in the generated files.

jordanhbuiltbyhq commented 1 year ago

Thanks @StarLard. Reading through these docs, it sounds when using CocoaPods with a project containing two app targets like mine, the only option is to use other and I'm required to create a target for my schema module. The docs don't describe how to do that. What do you choose in File > New Target - framework, library, something else? What files do I need to move into that target? How do you get that target compiled into in the app targets so both can use it?

calvincestari commented 1 year ago

Hi @jordanhbuiltbyhq, apologies that it's taken so long to respond to you.

Using the .other module type does require you to configure the pod yourself. Automating the creation of a pod required us to gather too much information in the configuration file; it's too big already. So for that module type you should already have the pod configured, linked with your project and simply waiting for the generated files in a predetermined locaiton.

Drag and drop the GraphQL folder into Xcode, choosing Create groups, and select the targets to add them to (there's two app targets in this project)

To do this type of manual addition to a project you should rather use the .embeddedInTarget(name:) module type.

jordanhbuiltbyhq commented 1 year ago

Thanks @calvincestari. Ideally, we should not need to create a pod to use GraphQL in our app. If that is required, do you have documentation that outlines how that can be done? I was under the impression it could be done with another target but perhaps not. Either way it's unfortunate, setup was much simpler in version 0.53.0.

To do this type of manual addition to a project you should rather use the .embeddedInTarget(name:) module type.

From what I've gathered, embeddedInTarget can only be used when you have a single target. We have a whitelisted app, so there's 2 app targets which allows us to customize the color scheme and such for each. If there's a way to simply embed the files in both targets like we did in 0.53.0 that'd be great.

calvincestari commented 1 year ago

Ideally, we should not need to create a pod to use GraphQL in our app. If that is required, do you have documentation that outlines how that can be done?

Cocoapods is not a requirement and every project is free to use the dependency manager that best suit its needs. If you do want to go the Cocoapods route, their guide to using Cocoapods will get you going.

I was under the impression it could be done with another target but perhaps not.

Our code generation definitely supports standard targets that you manage manually; for this I suggest the .embeddedInTarget(name:) module type. Also take a look at the documentation on output.schemaTypes.moduleType if you haven't already.

Either way it's unfortunate, setup was much simpler in version 0.53.0.

It was simpler because it did not support modularization. There was no way to share schema types between modules; it's one of the biggest pieces of feedback we received on the 0.x versions.

We're busy finishing up some documentation that should help resolve the perceived complexity in the configuration; migration guide, and a module type guide.

From what I've gathered, embeddedInTarget can only be used when you have a single target.

.embeddedInTarget(name:) shouldn't care about the number of targets in your project. Generated files are expected to be used within only a single target though, which, I think, is where things are not working for your configuration.

We have a whitelisted app, so there's 2 app targets which allows us to customize the color scheme and such for each. If there's a way to simply embed the files in both targets like we did in 0.53.0 that'd be great.

OK, understanding a bit more about your project configuration I recommend the following:

Let me know if that still doesn't work for your setup and we can try get a small sample project set up with the same configuration as yours so we can get it working for your case.

jordanhbuiltbyhq commented 1 year ago

We're busy finishing up some documentation that should help resolve the perceived complexity in the configuration; migration guide, and a module type guide.

This is good to hear! I did find it difficult to navigate the current docs trying to piece together everything needed to get setup, downloading the schema, code generation, etc.

.embeddedInTarget(name:) shouldn't care about the number of targets in your project. Generated files are expected to be used within only a single target though, which, I think, is where things are not working for your configuration.

I don't think I'm following this. I haven't tried to use embeddedInTarget(name:) because it needs the name of the target, but our app has two app targets it needs to work in both. Sounds like this isn't supported with our app's setup.

Pick the dependency manager that fits into your current workflow, project structure, etc.

CocoaPods would be our preferred solution due to other dependencies only supporting that, however with the complexity of needing to manually create and integrate a private pod now which we've never done before (alternatively the docs say "or manually creating Xcode targets" but no further information is available now on how to do that), I think I'll investigate adding SPM into the mix instead. It sounds like at least that will create the module on your behalf to be used included in both targets.

Thanks!

jordanhbuiltbyhq commented 1 year ago

Let me know if there's a better place for this, but trying out SPM, in Step 4 Setup and run code generation, in the expanded Swift Package Manager steps, after step 4 (link) it says:

By default, a directory containing your generated schema files will be located in a directory with the schema-name you provided; your generated operation and fragment files will be in the same location as the .graphql files they are defined by.

The second half is not true for SPM - no files were created in the same location as the .graphql file following those steps without modifying the config file, everything is in the schema-name directory. It created Packages.swift and a Sources directory containing Operations and Schema directories containing Swift files. At this point it doesn't say what you need to do next, except:

If your target is created in an Xcode project or workspace, you will need to manually add the generated files to your target.

I thought SPM created a module for you to hold these files, no need to manually add them to your target? What do I do from here? I need to surely integrate this generated package into both targets somehow.

There's a note under that:

Note: Because adding generated files to your Xcode targets must be done manually each time you generate new files, we highly recommend defining your project targets with SPM. Alternatively, you can generate your operations into the package that includes your schema files. For more information see the documentation for Code Generation Configuration.

The Code Generation Configuration link is broken throwing 404. The correct URL seems to be https://www.apollographql.com/docs/ios/code-generation/codegen-configuration.

calvincestari commented 1 year ago

.embeddedInTarget(name:) shouldn't care about the number of targets in your project. Generated files are expected to be used within only a single target though, which, I think, is where things are not working for your configuration.

I don't think I'm following this. I haven't tried to use embeddedInTarget(name:) because it needs the name of the target, but our app has two app targets it needs to work in both. Sounds like this isn't supported with our app's setup.

Yes this can be supported but embeddedInTarget(name:) is not the right choice then because as you said there is only a single target referenced.

By default, a directory containing your generated schema files will be located in a directory with the schema-name you provided; your generated operation and fragment files will be in the same location as the .graphql files they are defined by.

The second half is not true for SPM - no files were created in the same location as the .graphql file following those steps without modifying the config file, everything is in the schema-name directory. It created Packages.swift and a Sources directory containing Operations and Schema directories containing Swift files.

It appears that documentation hasn't been updated to match the new config defaults which were included in 1.0.4. .relative(subpath:) was the default for operations.output prior to 1.0.4. Thanks for highlighting this, I'll get it updated.

I thought SPM created a module for you to hold these files, no need to manually add them to your target? What do I do from here? I need to surely integrate this generated package into both targets somehow.

My understanding is that this is referring to the operation model files which before would have been outside the generated SPM package and therefore had to be manually added. Another place where we can improve the documentation, thanks for the feedback.

The Code Generation Configuration link is broken throwing 404. The correct URL seems to be https://www.apollographql.com/docs/ios/code-generation/codegen-configuration.

🤦🏻

calvincestari commented 1 year ago

My recommendation from this comment still sounds like it should work for your configuration:

OK, understanding a bit more about your project configuration I recommend the following:

  • Use either .swiftPackageManager or .other for output.schemaTypes.moduleType. The schema types files are always generated and since you're needing to share them between two targets it's best that they live in their own module. Pick the dependency manager that fits into your current workflow, project structure, etc. CocoaPods is not a requirement and SPM works just as well or better in some cases.

  • Use .relative(subpath:) or .absolute(path:) for output.operations. This will require the GraphQL operation files (queries, mutations, etc.) to be in each separate target, if they're unique to each target. If they are common operations that are used in either/all targets then use .inSchemaModule so they are placed into the schema types module and can be used in any other target that imports the schema module.

Pick the dependency manager that fits into your current workflow, project structure, etc.

What I mean by this is to choose the dependency manager that you already use to link to the Apollo iOS dependency. If you try use SPM to include the schema types module it will need to have a dependency on Apollo iOS because I don't think it'll be able to use the CocoaPods Apollo iOS dependency. You'll end up having two dependencies of Apollo iOS because of using different dependency managers.

The important part of the recommendation re. operations.output is where they're used. If operations are used exclusively in a particular module then .relative(subpath:) will work for you. If they're used in both modules then I recommend .inSchemaModule.

calvincestari commented 1 year ago

I'm going to close this issue though as it doesn't appear to be a bug but rather finding a combination of configuration options that work for your project. Let me know if you believe that's wrong.