int Driver:: run(std::vector<std::string> const &args, std::unordered_map<std::string, std::string> const &environment, Filesystem *filesystem, std::string const &workingDirectory) { Options options; std::pair<bool, std::string> result = libutil::Options::Parse<Options>(&options, args); if (!result.first) { fprintf(stderr, "error: %s\n", result.second.c_str()); return 1; } /* Validate options. */ if (!options.input()) { fprintf(stderr, "error: no input file specified\n"); return 1; } if (!options.output()) { fprintf(stderr, "error: no output file specified\n"); return 1; } pbxsetting::Environment settingsEnvironment = CreateBuildEnvironment(environment); /* Read in the input. */ std::vector<uint8_t> inputContents; if (!filesystem->read(&inputContents, FSUtil::ResolveRelativePath(*options.input(), workingDirectory))) { fprintf(stderr, "error: unable to read input %s\n", options.input()->c_str()); return 1; } /* Determine the input format. */ std::unique_ptr<plist::Format::Any> inputFormat = plist::Format::Any::Identify(inputContents); if (inputFormat == nullptr) { fprintf(stderr, "error: input %s is not a plist\n", options.input()->c_str()); return 1; } /* Deserialize the input. */ auto deserialize = plist::Format::Any::Deserialize(inputContents, *inputFormat); if (!deserialize.first) { fprintf(stderr, "error: %s: %s\n", options.input()->c_str(), deserialize.second.c_str()); return 1; } plist::Dictionary *root = plist::CastTo<plist::Dictionary>(deserialize.first.get()); if (root == nullptr) { fprintf(stderr, "error: info plist root is not a dictionary\n"); return 1; } /* * Expand all build settings in the plist. Build settings can be in dictionary keys * and strings. The resolved build setting values are passed through the environment. */ if (options.expandBuildSettings()) { ExpandBuildSettings(root, settingsEnvironment); } /* * Process additional content files. These are plists that get merged with the * main Info.plist at the top level. */ for (std::string const &additionalContentFile : options.additionalContentFiles()) { std::vector<uint8_t> contents; if (!filesystem->read(&contents, FSUtil::ResolveRelativePath(additionalContentFile, workingDirectory))) { fprintf(stderr, "error: unable to read additional content file: %s\n", additionalContentFile.c_str()); return 1; } auto additionalContent = plist::Format::Any::Deserialize(contents); if (additionalContent.first == nullptr) { fprintf(stderr, "error: unable to parse additional content file %s: %s\n", additionalContentFile.c_str(), additionalContent.second.c_str()); return 1; } if (plist::Dictionary *dictionary = plist::CastTo<plist::Dictionary>(additionalContent.first.get())) { /* Pass true to replace existing entries. */ root->merge(dictionary, true); } } /* * Info file keys/values: it's unknown what these are used for. Just warn. */ if (options.infoFileKeys() || options.infoFileValues()) { // TODO(grp): Handle info file keys and values. fprintf(stderr, "warning: info file keys and values are not yet implemented\n"); } /* * Platform and required architectures: it's unknown what these are used for. Just warn. */ if (options.platform() || !options.requiredArchitectures().empty()) { // TODO(grp): Handle platform and required architectures. #if 0 fprintf(stderr, "warning: platform and required architectures are not yet implemented\n"); #endif } /* * Add entries to the Info.plist from the build environment. */ AddBuildEnvironment(root, settingsEnvironment); /* * Write the PkgInfo file. This is just the package type and signature. */ if (options.genPkgInfo()) { auto result = WritePkgInfo(filesystem, root, FSUtil::ResolveRelativePath(*options.genPkgInfo(), workingDirectory)); if (!result.first) { fprintf(stderr, "error: %s\n", result.second.c_str()); return 1; } } /* * Copy the resource rules file. This is used by code signing. */ if (options.resourceRulesFile()) { std::string resourceRulesInputPath = settingsEnvironment.resolve("CODE_SIGN_RESOURCE_RULES_PATH"); if (!resourceRulesInputPath.empty()) { std::vector<uint8_t> contents; if (!filesystem->read(&contents, FSUtil::ResolveRelativePath(resourceRulesInputPath, workingDirectory))) { fprintf(stderr, "error: unable to read input %s\n", resourceRulesInputPath.c_str()); return 1; } if (!filesystem->write(contents, FSUtil::ResolveRelativePath(*options.resourceRulesFile(), workingDirectory))) { fprintf(stderr, "error: could not open output path %s to write\n", options.resourceRulesFile()->c_str()); return 1; } } } /* * Determine the output format. By default, use the same as the input format. */ plist::Format::Any outputFormat = *inputFormat; if (options.format()) { if (*options.format() == "binary") { outputFormat = plist::Format::Any::Create(plist::Format::Binary::Create()); } else if (*options.format() == "xml") { outputFormat = plist::Format::Any::Create(plist::Format::XML::Create(plist::Format::Encoding::UTF8)); } else if (*options.format() == "ascii" || *options.format() == "openstep") { outputFormat = plist::Format::Any::Create(plist::Format::ASCII::Create(false, plist::Format::Encoding::UTF8)); } else { fprintf(stderr, "error: unknown output format %s\n", options.format()->c_str()); return 1; } } /* Serialize the output. */ auto serialize = plist::Format::Any::Serialize(root, outputFormat); if (serialize.first == nullptr) { fprintf(stderr, "error: %s\n", serialize.second.c_str()); return 1; } /* Write out the output. */ if (!filesystem->write(*serialize.first, FSUtil::ResolveRelativePath(*options.output(), workingDirectory))) { fprintf(stderr, "error: could not open output path %s to write\n", options.output()->c_str()); return 1; } return 0; }