Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ead275e1 authored by Ryan Mitchell's avatar Ryan Mitchell Committed by Android (Google) Code Review
Browse files

Merge "AAPT2: Compile --zip flag"

parents a60283fd f3649d66
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -184,10 +184,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table
  std::unique_ptr<io::IFileCollectionIterator> iterator = apk_->Iterator();
  while (iterator->HasNext()) {
    io::IFile* file = iterator->Next();

    std::string path = file->GetSource().path;
    // The name of the path has the format "<zip-file-name>@<path-to-file>".
    path = path.substr(path.find('@') + 1);

    // Skip resources that are not referenced if requested.
    if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) {
+10 −2
Original line number Diff line number Diff line
@@ -31,12 +31,16 @@ namespace aapt {
struct Source {
  std::string path;
  Maybe<size_t> line;
  Maybe<std::string> archive;

  Source() = default;

  inline Source(const android::StringPiece& path) : path(path.to_string()) {  // NOLINT(implicit)
  }

  inline Source(const android::StringPiece& path, const android::StringPiece& archive)
      : path(path.to_string()), archive(archive.to_string()) {}

  inline Source(const android::StringPiece& path, size_t line)
      : path(path.to_string()), line(line) {}

@@ -45,10 +49,14 @@ struct Source {
  }

  std::string to_string() const {
    std::string s = path;
    if (archive) {
      s = ::android::base::StringPrintf("%s@%s", archive.value().c_str(), s.c_str());
    }
    if (line) {
      return ::android::base::StringPrintf("%s:%zd", path.c_str(), line.value());
      s = ::android::base::StringPrintf("%s:%zd", s.c_str(), line.value());
    }
    return path;
    return s;
  }
};

+138 −165
Original line number Diff line number Diff line
@@ -41,8 +41,10 @@
#include "format/proto/ProtoSerialize.h"
#include "io/BigBufferStream.h"
#include "io/FileStream.h"
#include "io/FileSystem.h"
#include "io/StringStream.h"
#include "io/Util.h"
#include "io/ZipArchive.h"
#include "util/Files.h"
#include "util/Maybe.h"
#include "util/Util.h"
@@ -135,81 +137,20 @@ static std::string BuildIntermediateContainerFilename(const ResourcePathData& da
  return name.str();
}

static bool IsHidden(const StringPiece& filename) {
  return util::StartsWith(filename, ".");
}

// Walks the res directory structure, looking for resource files.
static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
                                  std::vector<ResourcePathData>* out_path_data) {
  const std::string& root_dir = options.res_dir.value();
  std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
  if (!d) {
    context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: "
                                                           << SystemErrorCodeToString(errno));
    return false;
  }

  while (struct dirent* entry = readdir(d.get())) {
    if (IsHidden(entry->d_name)) {
      continue;
    }

    std::string prefix_path = root_dir;
    file::AppendPath(&prefix_path, entry->d_name);

    if (file::GetFileType(prefix_path) != file::FileType::kDirectory) {
      continue;
    }

    std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
    if (!subdir) {
      context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: "
                                                                << SystemErrorCodeToString(errno));
      return false;
    }

    while (struct dirent* leaf_entry = readdir(subdir.get())) {
      if (IsHidden(leaf_entry->d_name)) {
        continue;
      }

      std::string full_path = prefix_path;
      file::AppendPath(&full_path, leaf_entry->d_name);

      std::string err_str;
      Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str);
      if (!path_data) {
        context->GetDiagnostics()->Error(DiagMessage(full_path) << err_str);
        return false;
      }

      out_path_data->push_back(std::move(path_data.value()));
    }
  }

  // File-system directory enumeration order is platform-dependent. Sort the result to remove any
  // inconsistencies between platforms.
  std::sort(
      out_path_data->begin(), out_path_data->end(),
      [](const ResourcePathData& a, const ResourcePathData& b) { return a.source < b.source; });
  return true;
}

static bool CompileTable(IAaptContext* context, const CompileOptions& options,
                         const ResourcePathData& path_data, IArchiveWriter* writer,
                         const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
                         const std::string& output_path) {
  ResourceTable table;
  {
    FileInputStream fin(path_data.source.path);
    if (fin.HadError()) {
    auto fin = file->OpenInputStream();
    if (fin->HadError()) {
      context->GetDiagnostics()->Error(DiagMessage(path_data.source)
                                       << "failed to open file: " << fin.GetError());
          << "failed to open file: " << fin->GetError());
      return false;
    }

    // Parse the values file from XML.
    xml::XmlPullParser xml_parser(&fin);
    xml::XmlPullParser xml_parser(fin.get());

    ResourceParserOptions parser_options;
    parser_options.error_on_positional_arguments = !options.legacy_mode;
@@ -408,7 +349,7 @@ static bool IsValidFile(IAaptContext* context, const std::string& input_path) {
}

static bool CompileXml(IAaptContext* context, const CompileOptions& options,
                       const ResourcePathData& path_data, IArchiveWriter* writer,
                       const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
                       const std::string& output_path) {
  if (context->IsVerbose()) {
    context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
@@ -416,19 +357,18 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,

  std::unique_ptr<xml::XmlResource> xmlres;
  {
    FileInputStream fin(path_data.source.path);
    if (fin.HadError()) {
    auto fin = file->OpenInputStream();
    if (fin->HadError()) {
      context->GetDiagnostics()->Error(DiagMessage(path_data.source)
                                       << "failed to open file: " << fin.GetError());
                                       << "failed to open file: " << fin->GetError());
      return false;
    }

    xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source);
  }

    xmlres = xml::Inflate(fin.get(), context->GetDiagnostics(), path_data.source);
    if (!xmlres) {
      return false;
    }
  }

  xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
  xmlres->file.config = path_data.config;
@@ -508,7 +448,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,
}

static bool CompilePng(IAaptContext* context, const CompileOptions& options,
                       const ResourcePathData& path_data, IArchiveWriter* writer,
                       const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
                       const std::string& output_path) {
  if (context->IsVerbose()) {
    context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG");
@@ -522,15 +462,17 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
  res_file.type = ResourceFile::Type::kPng;

  {
    std::string content;
    if (!android::base::ReadFileToString(path_data.source.path, &content,
                                         true /*follow_symlinks*/)) {
      context->GetDiagnostics()->Error(DiagMessage(path_data.source)
                                       << "failed to open file: "
                                       << SystemErrorCodeToString(errno));
    auto data = file->OpenAsData();
    if (!data) {
      context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file ");
      return false;
    }

    // Read the file as a string
    char buffer_2[data->size()];
    memcpy(&buffer_2, data->data(), data->size());
    StringPiece content(buffer_2, data->size());

    BigBuffer crunched_png_buffer(4096);
    io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);

@@ -598,7 +540,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
    if (context->IsVerbose()) {
      // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
      // This will help catch exotic cases where the new code may generate larger PNGs.
      std::stringstream legacy_stream(content);
      std::stringstream legacy_stream(content.to_string());
      BigBuffer legacy_buffer(4096);
      Png png(context->GetDiagnostics());
      if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
@@ -612,41 +554,31 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
  }

  io::BigBufferInputStream buffer_in(&buffer);
  if (!WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
                                  context->GetDiagnostics())) {
    return false;
  }
  return true;
  return WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
      context->GetDiagnostics());
}

static bool CompileFile(IAaptContext* context, const CompileOptions& options,
                        const ResourcePathData& path_data, IArchiveWriter* writer,
                        const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
                        const std::string& output_path) {
  if (context->IsVerbose()) {
    context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file");
  }

  BigBuffer buffer(256);
  ResourceFile res_file;
  res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
  res_file.config = path_data.config;
  res_file.source = path_data.source;
  res_file.type = ResourceFile::Type::kUnknown;

  std::string error_str;
  Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
  if (!f) {
    context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to mmap file: "
                                     << error_str);
  auto data = file->OpenAsData();
  if (!data) {
    context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file ");
    return false;
  }

  io::MmappedData mmapped_in(std::move(f.value()));
  if (!WriteHeaderAndDataToWriter(output_path, res_file, &mmapped_in, writer,
                                  context->GetDiagnostics())) {
    return false;
  }
  return true;
  return WriteHeaderAndDataToWriter(output_path, res_file, data.get(), writer,
      context->GetDiagnostics());
}

class CompileContext : public IAaptContext {
@@ -701,6 +633,79 @@ class CompileContext : public IAaptContext {
  bool verbose_ = false;
};

int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* output_writer,
             CompileOptions& options) {
  bool error = false;

  // Iterate over the input files in a stable, platform-independent manner
  auto file_iterator  = inputs->Iterator();
  while (file_iterator->HasNext()) {
    auto file = file_iterator->Next();
    std::string path = file->GetSource().path;

    // Skip hidden input files
    if (file::IsHidden(path)) {
      continue;
    }

    if (!options.res_zip && !IsValidFile(context, path)) {
      error = true;
      continue;
    }

    // Extract resource type information from the full path
    std::string err_str;
    ResourcePathData path_data;
    if (auto maybe_path_data = ExtractResourcePathData(path, &err_str)) {
      path_data = maybe_path_data.value();
    } else {
      context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str);
      error = true;
      continue;
    }

    // Determine how to compile the file based on its type.
    auto compile_func = &CompileFile;
    if (path_data.resource_dir == "values" && path_data.extension == "xml") {
      compile_func = &CompileTable;
      // We use a different extension (not necessary anymore, but avoids altering the existing
      // build system logic).
      path_data.extension = "arsc";

    } else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
      if (*type != ResourceType::kRaw) {
        if (path_data.extension == "xml") {
          compile_func = &CompileXml;
        } else if ((!options.no_png_crunch && path_data.extension == "png")
                   || path_data.extension == "9.png") {
          compile_func = &CompilePng;
        }
      }
    } else {
      context->GetDiagnostics()->Error(DiagMessage()
          << "invalid file path '" << path_data.source << "'");
      error = true;
      continue;
    }

    // Treat periods as a reserved character that should not be present in a file name
    // Legacy support for AAPT which did not reserve periods
    if (compile_func != &CompileFile && !options.legacy_mode
        && std::count(path_data.name.begin(), path_data.name.end(), '.') != 0) {
      error = true;
      context->GetDiagnostics()->Error(DiagMessage(file->GetSource())
                                                    << "file name cannot contain '.' other than for"
                                                    << " specifying the extension");
      continue;
    }

    const std::string out_path = BuildIntermediateContainerFilename(path_data);
    error |= !compile_func(context, options, path_data, file, output_writer, out_path);
  }

  return error ? 1 : 0;
}

int CompileCommand::Action(const std::vector<std::string>& args) {
  CompileContext context(diagnostic_);
  context.SetVerbose(options_.verbose);
@@ -720,37 +725,55 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
    }
  }

  std::unique_ptr<io::IFileCollection> file_collection;
  std::unique_ptr<IArchiveWriter> archive_writer;

  std::vector<ResourcePathData> input_data;
  if (options_.res_dir) {
  // Collect the resources files to compile
  if (options_.res_dir && options_.res_zip) {
    context.GetDiagnostics()->Error(DiagMessage()
                                        << "only one of --dir and --zip can be specified");
    return 1;
  } else if (options_.res_dir) {
    if (!args.empty()) {
      // Can't have both files and a resource directory.
      context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
      Usage(&std::cerr);
      return 1;
    }

    if (!LoadInputFilesFromDir(&context, options_, &input_data)) {
    // Load the files from the res directory
    std::string err;
    file_collection = io::FileCollection::Create(options_.res_dir.value(), &err);
    if (!file_collection) {
      context.GetDiagnostics()->Error(DiagMessage(options_.res_dir.value()) << err);
      return 1;
    }

    archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
  } else if (options_.res_zip) {
    if (!args.empty()) {
      context.GetDiagnostics()->Error(DiagMessage() << "files given but --zip specified");
      Usage(&std::cerr);
      return 1;
    }

    // Load a zip file containing a res directory
    std::string err;
    file_collection = io::ZipFileCollection::Create(options_.res_zip.value(), &err);
    if (!file_collection) {
      context.GetDiagnostics()->Error(DiagMessage(options_.res_zip.value()) << err);
      return 1;
    }

    archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
  } else {
    input_data.reserve(args.size());
    auto collection = util::make_unique<io::FileCollection>();

    // Collect data from the path for each input file.
    for (const std::string& arg : args) {
      std::string error_str;
      if (Maybe<ResourcePathData> path_data = ExtractResourcePathData(arg, &error_str)) {
        input_data.push_back(std::move(path_data.value()));
      } else {
        context.GetDiagnostics()->Error(DiagMessage() << error_str << " (" << arg << ")");
        return 1;
      }
      collection->InsertFile(arg);
    }

    file_collection = std::move(collection);
    archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path);
  }

@@ -758,57 +781,7 @@ int CompileCommand::Action(const std::vector<std::string>& args) {
    return 1;
  }

  bool error = false;
  for (ResourcePathData& path_data : input_data) {
    if (options_.verbose) {
      context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing");
    }

    if (!IsValidFile(&context, path_data.source.path)) {
      error = true;
      continue;
    }

    // Determine how to compile the file based on its type.
    auto compile_func = &CompileFile;
    if (path_data.resource_dir == "values" && path_data.extension == "xml") {
      compile_func = &CompileTable;
      // We use a different extension (not necessary anymore, but avoids altering the existing
      // build system logic).
      path_data.extension = "arsc";

    } else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
      if (*type != ResourceType::kRaw) {
        if (path_data.extension == "xml") {
          compile_func = &CompileXml;
        } else if ((!options_.no_png_crunch && path_data.extension == "png")
            || path_data.extension == "9.png") {
          compile_func = &CompilePng;
        }
      }
    } else {
      context.GetDiagnostics()->Error(DiagMessage()
          << "invalid file path '" << path_data.source << "'");
      error = true;
      continue;
    }

    // Treat periods as a reserved character that should not be present in a file name
    // Legacy support for AAPT which did not reserve periods
    if (compile_func != &CompileFile && !options_.legacy_mode
        && std::count(path_data.name.begin(), path_data.name.end(), '.') != 0) {
      error = true;
      context.GetDiagnostics()->Error(DiagMessage() << "resource file '" << path_data.source.path
                                                    << "' name cannot contain '.' other than for"
                                                    << "specifying the extension");
      continue;
    }

    // Compile the file.
    const std::string out_path = BuildIntermediateContainerFilename(path_data);
    error |= !compile_func(&context, options_, path_data, archive_writer.get(), out_path);
  }
  return error ? 1 : 0;
  return Compile(&context, file_collection.get(), archive_writer.get(), options_);
}

}  // namespace aapt
+9 −2
Original line number Diff line number Diff line
@@ -18,7 +18,8 @@
#define AAPT2_COMPILE_H

#include "androidfw/StringPiece.h"

#include "format/Archive.h"
#include "process/IResourceTableConsumer.h"
#include "Command.h"
#include "Diagnostics.h"
#include "ResourceTable.h"
@@ -28,6 +29,7 @@ namespace aapt {
struct CompileOptions {
  std::string output_path;
  Maybe<std::string> res_dir;
  Maybe<std::string> res_zip;
  Maybe<std::string> generate_text_symbols_path;
  Maybe<Visibility::Level> visibility;
  bool pseudolocalize = false;
@@ -36,6 +38,7 @@ struct CompileOptions {
  bool verbose = false;
};

/** Parses flags and compiles resources to be used in linking.  */
class CompileCommand : public Command {
 public:
  explicit CompileCommand(IDiagnostics* diagnostic) : Command("compile", "c"),
@@ -43,6 +46,8 @@ class CompileCommand : public Command {
    SetDescription("Compiles resources to be linked into an apk.");
    AddRequiredFlag("-o", "Output path", &options_.output_path);
    AddOptionalFlag("--dir", "Directory to scan for resources", &options_.res_dir);
    AddOptionalFlag("--zip", "Zip file containing the res directory to scan for resources",
        &options_.res_zip);
    AddOptionalFlag("--output-text-symbols",
        "Generates a text file containing the resource symbols in the\n"
            "specified file", &options_.generate_text_symbols_path);
@@ -51,10 +56,10 @@ class CompileCommand : public Command {
    AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch);
    AddOptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
        &options_.legacy_mode);
    AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose);
    AddOptionalFlag("--visibility",
        "Sets the visibility of the compiled resources to the specified\n"
            "level. Accepted levels: public, private, default", &visibility_);
    AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose);
  }

  int Action(const std::vector<std::string>& args) override;
@@ -65,6 +70,8 @@ class CompileCommand : public Command {
  Maybe<std::string> visibility_;
};

int Compile(IAaptContext* context, io::IFileCollection* inputs,
             IArchiveWriter* output_writer, CompileOptions& options);
}// namespace aapt

#endif //AAPT2_COMPILE_H
+54 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@

#include "android-base/file.h"
#include "io/StringStream.h"
#include "io/ZipArchive.h"
#include "java/AnnotationProcessor.h"
#include "test/Test.h"

@@ -29,7 +30,6 @@ int TestCompile(const std::string& path, const std::string& outDir, bool legacy,
  args.push_back(path);
  args.push_back("-o");
  args.push_back(outDir);
  args.push_back("-v");
  if (legacy) {
    args.push_back("--legacy");
  }
@@ -94,4 +94,56 @@ TEST(CompilerTest, MultiplePeriods) {
  ASSERT_EQ(remove(path5_out.c_str()), 0);
}

TEST(CompilerTest, DirInput) {
  StdErrDiagnostics diag;
  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
  const std::string kResDir = android::base::Dirname(android::base::GetExecutablePath())
                            + "/integration-tests/CompileTest/DirInput/res";
  const std::string kOutputFlata = android::base::Dirname(android::base::GetExecutablePath())
                                 + "/integration-tests/CompileTest/DirInput/compiled.flata";
  remove(kOutputFlata.c_str());

  std::vector<android::StringPiece> args;
  args.push_back("--dir");
  args.push_back(kResDir);
  args.push_back("-o");
  args.push_back(kOutputFlata);
  ASSERT_EQ(CompileCommand(&diag).Execute(args, &std::cerr), 0);

  // Check for the presence of the compiled files
  std::string err;
  std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err);
  ASSERT_NE(zip, nullptr) << err;
  ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr);
  ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr);
  ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr);
  ASSERT_EQ(remove(kOutputFlata.c_str()), 0);
}

TEST(CompilerTest, ZipInput) {
  StdErrDiagnostics diag;
  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
  const std::string kResZip = android::base::Dirname(android::base::GetExecutablePath())
                            + "/integration-tests/CompileTest/ZipInput/res.zip";
  const std::string kOutputFlata = android::base::Dirname(android::base::GetExecutablePath())
                                 + "/integration-tests/CompileTest/ZipInput/compiled.flata";
  remove(kOutputFlata.c_str());

  std::vector<android::StringPiece> args;
  args.push_back("--zip");
  args.push_back(kResZip);
  args.push_back("-o");
  args.push_back(kOutputFlata);
  ASSERT_EQ(CompileCommand(&diag).Execute(args, &std::cerr), 0);

  // Check for the presence of the compiled files
  std::string err;
  std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(kOutputFlata, &err);
  ASSERT_NE(zip, nullptr) << err;
  ASSERT_NE(zip->FindFile("drawable_image.png.flat"), nullptr);
  ASSERT_NE(zip->FindFile("layout_layout.xml.flat"), nullptr);
  ASSERT_NE(zip->FindFile("values_values.arsc.flat"), nullptr);
  ASSERT_EQ(remove(kOutputFlata.c_str()), 0);
}

} // namespace aapt
 No newline at end of file
Loading