AAPT2: Separate out the various steps
An early refactor. Some ideas became clearer as
development continued. Now the various phases are much
clearer and more easily reusable.
Also added a ton of tests!
Change-Id: Ic8f0a70c8222370352e63533b329c40457c0903e
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
new file mode 100644
index 0000000..a81dc7b
--- /dev/null
+++ b/tools/aapt2/util/Files.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "util/Files.h"
+#include "util/Util.h"
+
+#include <cerrno>
+#include <cstdio>
+#include <dirent.h>
+#include <string>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+// Windows includes.
+#include <direct.h>
+#endif
+
+namespace aapt {
+namespace file {
+
+FileType getFileType(const StringPiece& path) {
+ struct stat sb;
+ if (stat(path.data(), &sb) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR) {
+ return FileType::kNonexistant;
+ }
+ return FileType::kUnknown;
+ }
+
+ if (S_ISREG(sb.st_mode)) {
+ return FileType::kRegular;
+ } else if (S_ISDIR(sb.st_mode)) {
+ return FileType::kDirectory;
+ } else if (S_ISCHR(sb.st_mode)) {
+ return FileType::kCharDev;
+ } else if (S_ISBLK(sb.st_mode)) {
+ return FileType::kBlockDev;
+ } else if (S_ISFIFO(sb.st_mode)) {
+ return FileType::kFifo;
+#if defined(S_ISLNK)
+ } else if (S_ISLNK(sb.st_mode)) {
+ return FileType::kSymlink;
+#endif
+#if defined(S_ISSOCK)
+ } else if (S_ISSOCK(sb.st_mode)) {
+ return FileType::kSocket;
+#endif
+ } else {
+ return FileType::kUnknown;
+ }
+}
+
+std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) {
+ DIR* dir = opendir(root.data());
+ if (dir == nullptr) {
+ if (outError) {
+ std::stringstream errorStr;
+ errorStr << "unable to open file: " << strerror(errno);
+ *outError = errorStr.str();
+ return {};
+ }
+ }
+
+ std::vector<std::string> files;
+ dirent* entry;
+ while ((entry = readdir(dir))) {
+ files.emplace_back(entry->d_name);
+ }
+
+ closedir(dir);
+ return files;
+}
+
+inline static int mkdirImpl(const StringPiece& path) {
+#ifdef _WIN32
+ return _mkdir(path.toString().c_str());
+#else
+ return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
+#endif
+}
+
+bool mkdirs(const StringPiece& path) {
+ const char* start = path.begin();
+ const char* end = path.end();
+ for (const char* current = start; current != end; ++current) {
+ if (*current == sDirSep) {
+ StringPiece parentPath(start, current - start);
+ int result = mkdirImpl(parentPath);
+ if (result < 0 && errno != EEXIST) {
+ return false;
+ }
+ }
+ }
+ return mkdirImpl(path) == 0 || errno == EEXIST;
+}
+
+StringPiece getStem(const StringPiece& path) {
+ const char* start = path.begin();
+ const char* end = path.end();
+ for (const char* current = end - 1; current != start - 1; --current) {
+ if (*current == sDirSep) {
+ return StringPiece(start, current - start);
+ }
+ }
+ return {};
+}
+
+StringPiece getFilename(const StringPiece& path) {
+ const char* end = path.end();
+ const char* lastDirSep = path.begin();
+ for (const char* c = path.begin(); c != end; ++c) {
+ if (*c == sDirSep) {
+ lastDirSep = c + 1;
+ }
+ }
+ return StringPiece(lastDirSep, end - lastDirSep);
+}
+
+StringPiece getExtension(const StringPiece& path) {
+ StringPiece filename = getFilename(path);
+ const char* const end = filename.end();
+ const char* c = std::find(filename.begin(), end, '.');
+ if (c != end) {
+ return StringPiece(c, end - c);
+ }
+ return {};
+}
+
+std::string packageToPath(const StringPiece& package) {
+ std::string outPath;
+ for (StringPiece part : util::tokenize<char>(package, '.')) {
+ appendPath(&outPath, part);
+ }
+ return outPath;
+}
+
+Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) {
+ std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose };
+ if (!f) {
+ if (outError) *outError = strerror(errno);
+ return {};
+ }
+
+ int fd = fileno(f.get());
+
+ struct stat fileStats = {};
+ if (fstat(fd, &fileStats) != 0) {
+ if (outError) *outError = strerror(errno);
+ return {};
+ }
+
+ android::FileMap fileMap;
+ if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) {
+ if (outError) *outError = strerror(errno);
+ return {};
+ }
+ return std::move(fileMap);
+}
+
+bool FileFilter::setPattern(const StringPiece& pattern) {
+ mPatternTokens = util::splitAndLowercase(pattern, ':');
+ return true;
+}
+
+bool FileFilter::operator()(const std::string& filename, FileType type) const {
+ if (filename == "." || filename == "..") {
+ return false;
+ }
+
+ const char kDir[] = "dir";
+ const char kFile[] = "file";
+ const size_t filenameLen = filename.length();
+ bool chatty = true;
+ for (const std::string& token : mPatternTokens) {
+ const char* tokenStr = token.c_str();
+ if (*tokenStr == '!') {
+ chatty = false;
+ tokenStr++;
+ }
+
+ if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) {
+ if (type != FileType::kDirectory) {
+ continue;
+ }
+ tokenStr += sizeof(kDir);
+ }
+
+ if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) {
+ if (type != FileType::kRegular) {
+ continue;
+ }
+ tokenStr += sizeof(kFile);
+ }
+
+ bool ignore = false;
+ size_t n = strlen(tokenStr);
+ if (*tokenStr == '*') {
+ // Math suffix.
+ tokenStr++;
+ n--;
+ if (n <= filenameLen) {
+ ignore = strncasecmp(tokenStr, filename.c_str() + filenameLen - n, n) == 0;
+ }
+ } else if (n > 1 && tokenStr[n - 1] == '*') {
+ // Match prefix.
+ ignore = strncasecmp(tokenStr, filename.c_str(), n - 1) == 0;
+ } else {
+ ignore = strcasecmp(tokenStr, filename.c_str()) == 0;
+ }
+
+ if (ignore) {
+ if (chatty) {
+ mDiag->warn(DiagMessage() << "skipping "
+ << (type == FileType::kDirectory ? "dir '" : "file '")
+ << filename << "' due to ignore pattern '"
+ << token << "'");
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace file
+} // namespace aapt