diff --git a/build/workspaces/build-osx-bundle.sh b/build/workspaces/build-osx-bundle.sh index 296c6c7f96..d8a0c12020 100755 --- a/build/workspaces/build-osx-bundle.sh +++ b/build/workspaces/build-osx-bundle.sh @@ -22,13 +22,13 @@ # Choices are "x86_64" or "i386" (ppc and ppc64 not supported) export ARCH=${ARCH:="x86_64"} -OSX_VERSION=`sw_vers -productVersion | grep -Eo "^\d+.\d+"` +OSX_VERSION='10.12' # Set SDK and mimimum required OS X version export SYSROOT=${SYSROOT:="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX$OSX_VERSION.sdk"} export MIN_OSX_VERSION=${MIN_OSX_VERSION:="10.7"} # 0 A.D. release version, e.g. Alpha 21 is 0.0.21 -BUNDLE_VERSION=${BUNDLE_VERSION:="0.0.X"} +BUNDLE_VERSION=${BUNDLE_VERSION:="0.0.21"} # Define compiler as "clang", this is all Mavericks supports. # gcc symlinks may still exist, but they are simply clang with @@ -104,10 +104,17 @@ BUNDLE_SHAREDSUPPORT=$BUNDLE_CONTENTS/SharedSupport # TODO: Do we really want to regenerate everything? (consider if one task fails) # Build libraries against SDK -echo "\nBuilding libraries\n" -pushd ../../libraries/osx > /dev/null -./build-osx-libs.sh $JOBS --force-rebuild >> $build_log 2>&1 || die "Libraries build script failed" -popd > /dev/null +echo "\nSymlinking libraries\n" +cd ../../ +ln -s /Users/Lancelot/Desktop/git-0AD/libraries +cd binaries/data/mods/public/ +ln -s /Users/Lancelot/Desktop/git-0AD/binaries/data/mods/public/art +ln -s /Users/Lancelot/Desktop/git-0AD/binaries/data/mods/public/audio +cd ../../../../build/workspaces +#echo "\nBuilding libraries\n" +#pushd ../../libraries/osx > /dev/null +#./build-osx-libs.sh $JOBS --force-rebuild >> $build_log 2>&1 || die "Libraries build script failed" +#popd > /dev/null # Clean and update workspaces echo "\nGenerating workspaces\n" @@ -117,6 +124,8 @@ echo "\nGenerating workspaces\n" pushd gcc > /dev/null echo "\nBuilding game\n" +patch -p0 -i "/Users/Lancelot/diff.patch" +patch -p0 -i "/Users/Lancelot/diff2.patch" (make clean && CC="$CC -arch $ARCH" CXX="$CXX -arch $ARCH" make ${JOBS}) >> $build_log 2>&1 || die "Game build failed!" popd > /dev/null @@ -211,7 +220,7 @@ PlistBuddy -c "Add :CFBundleDevelopmentRegion string English" ${INFO_PLIST} PlistBuddy -c "Add :CFBundleInfoDictionaryVersion string 6.0" ${INFO_PLIST} PlistBuddy -c "Add :CFBundleIconFile string 0ad" ${INFO_PLIST} PlistBuddy -c "Add :LSMinimumSystemVersion string ${BUNDLE_MIN_OSX_VERSION}" ${INFO_PLIST} -PlistBuddy -c "Add :NSHumanReadableCopyright string Copyright © 2015 Wildfire Games" ${INFO_PLIST} +PlistBuddy -c "Add :NSHumanReadableCopyright string Copyright © 2016 Wildfire Games" ${INFO_PLIST} # TODO: Automatically create compressed DMG with hdiutil? # (this is a bit complicated so I do it manually for now) diff --git a/source/lib/file/vfs/tests/test_vfs_tree.h b/source/lib/file/vfs/tests/test_vfs_tree.h index 7d4084b42c..17a17fd903 100644 --- a/source/lib/file/vfs/tests/test_vfs_tree.h +++ b/source/lib/file/vfs/tests/test_vfs_tree.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (c) 2016 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -22,8 +22,10 @@ #include "lib/self_test.h" -#include "lib/file/vfs/vfs_tree.h" #include "lib/file/common/file_loader.h" +#include "lib/file/vfs/vfs.h" +#include "lib/file/vfs/vfs_lookup.h" +#include "lib/file/vfs/vfs_tree.h" class MockLoader : public IFileLoader { @@ -43,6 +45,99 @@ public: class TestVfsTree : public CxxTest::TestSuite { + /** + * We create a few different "mods" here to test proper .DELETED + * behavior. + * + * To check which file is used we use the priority. + * + * 1 + * +--a + * +--b/ + * | +--a + * | \--b/ + * | \--a + * \--c/ + * +--a + * \--b + * + * 2 + * +--a.DELETED + * +--b/ + * | +--a + * | \--b.DELETED + * +--c.DELETED + * \--c/ + * +--a + * \--b + * + * 3 + * +--a + * \--b/ + * \--b/ + * \--a + */ + void mount_mod(size_t mod, VfsDirectory& dir) + { + size_t priority = mod; + PIFileLoader loader(new MockLoader(1)); + switch(mod) + { + case 1: + { + dir.AddFile(VfsFile("a", 0, 0, priority, loader)); + VfsDirectory* b = dir.AddSubdirectory("b"); + b->AddFile(VfsFile("a", 0, 0, priority, loader)); + VfsDirectory* b_b = b->AddSubdirectory("b"); + b_b->AddFile(VfsFile("a", 0, 0, priority, loader)); + VfsDirectory* c = dir.AddSubdirectory("c"); + c->AddFile(VfsFile("a", 0, 0, priority, loader)); + c->AddFile(VfsFile("b", 0, 0, priority, loader)); + break; + } + case 2: + { + dir.DeleteSubtree(VfsFile("a.DELETED", 0, 0, priority, loader)); + VfsDirectory* b = dir.AddSubdirectory("b"); + b->AddFile(VfsFile("a", 0, 0, priority, loader)); + b->DeleteSubtree(VfsFile("b.DELETED", 0, 0, priority, loader)); + dir.DeleteSubtree(VfsFile("c.DELETED", 0, 0, priority, loader)); + VfsDirectory* c = dir.AddSubdirectory("c"); + c->AddFile(VfsFile("a", 0, 0, priority, loader)); + c->AddFile(VfsFile("b", 0, 0, priority, loader)); + break; + } + case 3: + { + dir.AddFile(VfsFile("a", 0, 0, priority, loader)); + VfsDirectory* b = dir.AddSubdirectory("b"); + VfsDirectory* b_b = b->AddSubdirectory("b"); + b_b->AddFile(VfsFile("a", 0, 0, priority, loader)); + break; + } + NODEFAULT; + } + } + + void check_priority(VfsDirectory& root, const VfsPath& path, size_t priority) + { + VfsDirectory* dir; VfsFile* file; + TS_ASSERT_OK(vfs_Lookup(path, &root, dir, &file, VFS_LOOKUP_SKIP_POPULATE)); + TS_ASSERT_EQUALS(file->Priority(), priority); + } + + void file_does_not_exists(VfsDirectory& root, const VfsPath& path) + { + VfsDirectory* dir; VfsFile* file; + TS_ASSERT_EQUALS(vfs_Lookup(path, &root, dir, &file, VFS_LOOKUP_SKIP_POPULATE), ERR::VFS_FILE_NOT_FOUND); + } + + void directory_exists(VfsDirectory& root, const VfsPath& path, Status error = INFO::OK) + { + VfsDirectory* dir; + TS_ASSERT_EQUALS(vfs_Lookup(path, &root, dir, nullptr, VFS_LOOKUP_SKIP_POPULATE), error); + } + public: void test_replacement() { @@ -62,4 +157,35 @@ public: TS_ASSERT_EQUALS(dir.AddFile(file2)->MTime(), file2.MTime()); TS_ASSERT_EQUALS(dir.AddFile(file1)->MTime(), file2.MTime()); } + + void test_deleted() + { + VfsDirectory dir; + + mount_mod(1, dir); + mount_mod(2, dir); + file_does_not_exists(dir, "a"); + check_priority(dir, "b/a", 2); + directory_exists(dir, "b/b/", ERR::VFS_DIR_NOT_FOUND); + directory_exists(dir, "c/"); + check_priority(dir, "c/a", 2); + check_priority(dir, "c/b", 2); + dir.Clear(); + + + mount_mod(1, dir); + mount_mod(2, dir); + mount_mod(3, dir); + check_priority(dir, "a", 3); + check_priority(dir, "b/b/a", 3); + dir.Clear(); + + + mount_mod(1, dir); + mount_mod(3, dir); + mount_mod(2, dir); + check_priority(dir, "a", 3); + check_priority(dir, "b/b/a", 3); + dir.Clear(); + } }; diff --git a/source/lib/file/vfs/vfs_populate.cpp b/source/lib/file/vfs/vfs_populate.cpp index 9980e17e5d..c00febeccf 100644 --- a/source/lib/file/vfs/vfs_populate.cpp +++ b/source/lib/file/vfs/vfs_populate.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (c) 2016 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -58,13 +58,13 @@ public: DirectoryNames subdirectoryNames; subdirectoryNames.reserve(50); RETURN_STATUS_IF_ERR(GetDirectoryEntries(m_realDirectory->Path(), &files, &subdirectoryNames)); - // to support .DELETED files inside archives safely, we need to load - // archives and loose files in a deterministic order in case they add - // and then delete the same file (or vice versa, depending on loading - // order). GetDirectoryEntries has undefined order so sort its output - std::sort(files.begin(), files.end(), CompareFileInfoByName()); - std::sort(subdirectoryNames.begin(), subdirectoryNames.end()); - + // Since .DELETED files only remove files in lower priority mods + // loose files and archive files have no conflicts so we do not need + // to sort them. + // We add directories after they might have been removed by .DELETED + // files (as they did not contain any files at that point). The order + // of GetDirectoryEntries is undefined, but that does not really matter (TODO really?) + // so we do not need to sort its output. RETURN_STATUS_IF_ERR(AddFiles(files)); AddSubdirectories(subdirectoryNames); @@ -75,14 +75,14 @@ private: void AddFile(const CFileInfo& fileInfo) const { const VfsPath name = fileInfo.Name(); + const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), m_realDirectory->Priority(), m_realDirectory); if(name.Extension() == L".DELETED") { - m_directory->RemoveFile(name.Basename()); + m_directory->DeleteSubtree(file); if(!(m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED)) return; } - const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), m_realDirectory->Priority(), m_realDirectory); m_directory->AddFile(file); } @@ -97,14 +97,14 @@ private: WARN_IF_ERR(vfs_Lookup(pathname, this_->m_directory, directory, 0, flags)); const VfsPath name = fileInfo.Name(); + const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), this_->m_realDirectory->Priority(), archiveFile); if(name.Extension() == L".DELETED") { - directory->RemoveFile(name.Basename()); + directory->DeleteSubtree(file); if(!(this_->m_realDirectory->Flags() & VFS_MOUNT_KEEP_DELETED)) return; } - const VfsFile file(name, (size_t)fileInfo.Size(), fileInfo.MTime(), this_->m_realDirectory->Priority(), archiveFile); directory->AddFile(file); } diff --git a/source/lib/file/vfs/vfs_tree.cpp b/source/lib/file/vfs/vfs_tree.cpp index fdda228119..c0d0b2ec0e 100644 --- a/source/lib/file/vfs/vfs_tree.cpp +++ b/source/lib/file/vfs/vfs_tree.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2013 Wildfire Games +/* Copyright (c) 2016 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -49,6 +49,13 @@ VfsDirectory::VfsDirectory() { } +static bool ShouldDelete(const VfsFile& file, const VfsFile& deletedFile) +{ + // We only check priority here, a .DELETED file in a mod should not + // delete files in that mod. For the same reason we ignore loose + // .DELETED files next to an archive. + return file.Priority() < deletedFile.Priority(); +} static bool ShouldReplaceWith(const VfsFile& previousFile, const VfsFile& newFile) { @@ -78,6 +85,38 @@ static bool ShouldReplaceWith(const VfsFile& previousFile, const VfsFile& newFil } +void VfsDirectory::DeleteSubtree(const VfsFile& file) +{ + ENSURE(file.Name().Extension() == L".DELETED"); + + const VfsPath basename = file.Name().Basename(); + std::map::iterator fit = m_files.find(basename); + if(fit != m_files.end() && ShouldDelete(fit->second, file)) + m_files.erase(basename); + + std::map::iterator dit = m_subdirectories.find(basename); + if(dit != m_subdirectories.end() && dit->second.DeleteTree(file)) + m_subdirectories.erase(dit); +} + +bool VfsDirectory::DeleteTree(const VfsFile& file) +{ + for(std::map::iterator it = m_files.begin(); it != m_files.end();) + if(ShouldDelete(it->second, file)) + it = m_files.erase(it); + else + ++it; + + for(std::map::iterator it = m_subdirectories.begin(); it != m_subdirectories.end();) + if(it->second.DeleteTree(file)) + it = m_subdirectories.erase(it); + else + ++it; + + return m_files.empty() && m_subdirectories.empty(); +} + + VfsFile* VfsDirectory::AddFile(const VfsFile& file) { std::pair value = std::make_pair(file.Name(), file); @@ -122,7 +161,6 @@ VfsFile* VfsDirectory::GetFile(const VfsPath& name) return &it->second; } - VfsDirectory* VfsDirectory::GetSubdirectory(const VfsPath& name) { VfsSubdirectories::iterator it = m_subdirectories.find(name.string()); diff --git a/source/lib/file/vfs/vfs_tree.h b/source/lib/file/vfs/vfs_tree.h index 071c402828..4643d4b377 100644 --- a/source/lib/file/vfs/vfs_tree.h +++ b/source/lib/file/vfs/vfs_tree.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010 Wildfire Games +/* Copyright (c) 2016 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the @@ -77,12 +77,28 @@ private: class VfsDirectory { + /** + * remove all files with a lower priority than @p file + * and do the same for all subdirectories recursively. + * @return true if the directory is empty afterwards + **/ + bool DeleteTree(const VfsFile& file); + public: typedef std::map VfsFiles; typedef std::map VfsSubdirectories; VfsDirectory(); + /** + * remove the given file or subdirectory according to the priority of the + * passed .DELETED file. + * CAUTION: Invalidates all previously returned pointers of the file or + * subdirectory (and contents) if those have lower priority + * than @p file. + **/ + void DeleteSubtree(const VfsFile& file); + /** * @return address of existing or newly inserted file. **/ diff --git a/source/ps/ArchiveBuilder.cpp b/source/ps/ArchiveBuilder.cpp index 3317f4ae78..2db884c700 100644 --- a/source/ps/ArchiveBuilder.cpp +++ b/source/ps/ArchiveBuilder.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2016 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -77,11 +77,9 @@ void CArchiveBuilder::Build(const OsPath& archive, bool compress) CXeromyces xero; - for (size_t i = 0; i < m_Files.size(); ++i) + for (const VfsPath& path : m_Files) { Status ret; - - const VfsPath path = m_Files[i]; OsPath realPath; ret = m_VFS->GetRealPath(path, realPath); ENSURE(ret == INFO::OK);